Skip to main content

Vec3

Struct Vec3 

Source
#[repr(C)]
pub struct Vec3 { pub x: f32, pub y: f32, pub z: f32, }
Expand description

A 3-dimensional vector.

Fields§

§x: f32§y: f32§z: f32

Implementations§

Source§

impl Vec3

Source

pub const ZERO: Vec3

All zeroes.

Source

pub const ONE: Vec3

All ones.

Source

pub const NEG_ONE: Vec3

All negative ones.

Source

pub const MIN: Vec3

All f32::MIN.

Source

pub const MAX: Vec3

All f32::MAX.

Source

pub const NAN: Vec3

All f32::NAN.

Source

pub const INFINITY: Vec3

All f32::INFINITY.

Source

pub const NEG_INFINITY: Vec3

All f32::NEG_INFINITY.

Source

pub const X: Vec3

A unit vector pointing along the positive X axis.

Source

pub const Y: Vec3

A unit vector pointing along the positive Y axis.

Source

pub const Z: Vec3

A unit vector pointing along the positive Z axis.

Source

pub const NEG_X: Vec3

A unit vector pointing along the negative X axis.

Source

pub const NEG_Y: Vec3

A unit vector pointing along the negative Y axis.

Source

pub const NEG_Z: Vec3

A unit vector pointing along the negative Z axis.

Source

pub const AXES: [Vec3; 3]

The unit axes.

Source

pub const USES_CORE_SIMD: bool = false

Vec3 uses Rust Portable SIMD

Source

pub const USES_NEON: bool = false

Vec3 uses Arm NEON

Source

pub const USES_SCALAR_MATH: bool = true

Vec3 uses scalar math

Source

pub const USES_SSE2: bool = false

Vec3 uses Intel SSE2

Source

pub const USES_WASM_SIMD: bool = false

Vec3 uses WebAssembly 128-bit SIMD

Source

pub const USES_WASM32_SIMD: bool = false

👎Deprecated since 0.31.0:

Renamed to USES_WASM_SIMD

Source

pub const fn new(x: f32, y: f32, z: f32) -> Vec3

Creates a new vector.

Examples found in repository?
examples/animation.rs (line 80)
80const ELF_START: Vec3 = Vec3::new(-3.0, 0.0, 4.0);
81
82/// Ground positions of the three hurt patches, and their radius.
83const HURT_PATCHES: [Vec3; 3] = [
84    Vec3::new(1.5, 0.0, -1.0),
85    Vec3::new(-1.5, 0.0, -3.5),
86    Vec3::new(3.0, 0.0, 2.0),
87];
88const HURT_RADIUS: f32 = 0.9;
89/// Hits it takes before the elf reads `Death` instead of `Hit`.
90const FATAL_HITS: u32 = 3;
91
92/// The seat's position, at the ground, and its footprint: measured on the
93/// asset, `sit_down` lowers the pelvis from `0.77` meters to `0.47` meters
94/// and moves it `0.29` meters toward the seat, the feet staying where they
95/// stood, so a block this tall under that landing puts the pelvis on its
96/// top face.
97const SEAT_POSITION: Vec3 = Vec3::new(-3.5, 0.0, -3.0);
98const SEAT_FOOTPRINT: f32 = 1.0;
99const SEAT_HEIGHT: f32 = 0.45;
100/// How tall the figure sits, from the seat's top to its head.
101const SEATED_HEIGHT: f32 = 0.75;
102/// Height from the ground to the seated elf's head.
103const SEAT_HEAD_HEIGHT: f32 = SEAT_HEIGHT + SEATED_HEIGHT;
104/// The gap in front of the seat's own face the elf stands at.
105const SEAT_STAND_CLEARANCE: f32 = 0.05;
106/// Where the elf stands to sit on the seat, at its front face plus
107/// [`SEAT_STAND_CLEARANCE`], and which way it faces there: away from the
108/// seat, so `sit_down` moves the pelvis back onto it.
109const SEAT_SPOT: Vec3 = Vec3::new(
110    SEAT_POSITION.x,
111    0.0,
112    SEAT_POSITION.z + SEAT_FOOTPRINT * 0.5 + SEAT_STAND_CLEARANCE,
113);
114const SEAT_FACING: f32 = 0.0;
115/// The least distance from [`SEAT_POSITION`] `Button::Interact` sits the
116/// elf down at.
117const SEAT_INTERACT_RADIUS: f32 = 1.6;
118
119/// The second elf's fixed position, under a machine scrubbed by its
120/// distance from the first.
121const SCRUBBED_ELF_POSITION: Vec3 = Vec3::new(3.5, 0.0, 4.0);
122/// The distance at and under which the scrubbed elf reads fully seated.
123const SCRUB_NEAR: f32 = 1.5;
124/// The distance at and past which it reads fully standing.
125const SCRUB_FAR: f32 = 5.0;
126
127/// The lamp post's own position, near the seat and its stand.
128const LAMP_POST_POSITION: Vec3 = Vec3::new(-4.9, 0.0, -2.0);
129const LAMP_POST_HEIGHT: f32 = 2.2;
130const LAMP_POST_THICKNESS: f32 = 0.16;
131const LAMP_POST_COLOR: Color = Color::rgb(0.16, 0.14, 0.12);
132/// The lamp's own head, on top of the post, emissive in [`LAMP_LIGHT_COLOR`].
133const LAMP_HEAD_SIZE: f32 = 0.34;
134/// The gap left between the post's own top and the head's bottom face, so
135/// the light sits clear of both meshes rather than inside the head it
136/// would then cast no light from.
137const LAMP_HEAD_GAP: f32 = 0.06;
138/// Past `1.0`, so its glow lands on the ground near it, visible against
139/// the sky, and the head reads bright once bloom spreads it.
140const LAMP_LIGHT_COLOR: Color = Color::rgb(5.5, 4.2, 2.2);
141const LAMP_LIGHT_RANGE: f32 = 6.0;
142
143/// Centered over the three [`HURT_PATCHES`], tall enough for one cone to
144/// reach all of them.
145const SPOT_POSITION: Vec3 = Vec3::new(1.0, 6.0, -0.83);
146const SPOT_DIRECTION: Vec3 = Vec3::NEG_Y;
147/// Past `1.0`, so the cone is visible on the ground against the sky, and
148/// bright enough that a patch inside it reads well past a patch outside.
149const SPOT_COLOR: Color = Color::rgb(11.0, 9.8, 8.2);
150const SPOT_RANGE: f32 = 9.0;
151const SPOT_ANGLE: f32 = 0.85;
152/// The fixture's own edge length, drawn where the cone starts.
153const SPOT_FIXTURE_SIZE: f32 = 0.22;
154const SPOT_FIXTURE_COLOR: Color = Color::rgb(0.2, 0.2, 0.22);
155
156/// The butterfly's own source, next to the other example assets.
157const BUTTERFLY_SOURCE: &str = "examples/assets/butterfly.glb";
158/// The root node the source names the butterfly under.
159const BUTTERFLY_ROOT: &str = "Butterfly";
160/// The point halfway between [`SEAT_POSITION`] and [`LAMP_POST_POSITION`],
161/// the closed path's own center.
162const BUTTERFLY_CENTER: Vec3 = Vec3::new(-4.2, 0.0, -2.5);
163/// The closed path's radius along `x` and `z`, wide enough to loop around
164/// both the seat and the lamp.
165const BUTTERFLY_RADIUS: Vec2 = Vec2::new(1.8, 1.4);
166/// About the lamp's own height.
167const BUTTERFLY_HEIGHT: f32 = LAMP_POST_HEIGHT;
168/// Radians a second around the path; a full loop takes about 14 seconds.
169const BUTTERFLY_ANGULAR_SPEED: f32 = TAU / 14.0;
170/// Past `1.0`, so its glow lands on the ground and the lamp post it passes.
171const BUTTERFLY_LIGHT_COLOR: Color = Color::rgb(1.8, 5.5, 5.0);
172const BUTTERFLY_LIGHT_RANGE: f32 = 3.0;
173/// The butterfly's own small emissive, so it reads bright rather than
174/// dark against the glow it casts.
175const BUTTERFLY_EMISSIVE: Color = Color::rgb(0.6, 1.8, 1.6);
176
177const GROUND_SIZE: f32 = 400.0;
178const GROUND_COLOR: Color = Color::rgb(0.24, 0.30, 0.22);
179const HURT_COLOR: Color = Color::rgb(0.75, 0.12, 0.10);
180const SEAT_COLOR: Color = Color::rgb(0.5, 0.42, 0.3);
181/// Low, near the horizon, and dim.
182const SUN_DIRECTION: Vec3 = Vec3::new(-0.85, -0.18, -0.5);
183const SUN_COLOR: Color = Color::rgb(0.55, 0.32, 0.22);
184const SKY_ZENITH: Color = Color::rgb(0.06, 0.07, 0.2);
185const SKY_HORIZON: Color = Color::rgb(0.55, 0.35, 0.28);
186const SKY_NADIR: Color = Color::rgb(0.05, 0.05, 0.07);
187/// The fraction of its own light the sky lands and reflects: dim, so the
188/// lamp, spotlight and butterfly lights read against it.
189const SKY_LIGHT: f32 = 0.15;
190
191/// The orbit camera's distance behind and height above its target.
192const CAMERA_BACK: f32 = 3.4;
193const CAMERA_UP: f32 = 1.7;
194/// Height above the ground the camera looks at, framing the whole figure.
195const CAMERA_LOOK_HEIGHT: f32 = 0.8;
196const CAMERA_FOV: f32 = 50.0;
197/// Radians the camera orbits, or tilts, per pixel the pointer moves,
198/// chosen so a drag the width, or the height, of the window turns it by
199/// [`CAMERA_YAW_PER_DRAG`], or [`CAMERA_PITCH_PER_DRAG`].
200const CAMERA_YAW_PER_DRAG: f32 = core::f32::consts::PI;
201const CAMERA_PITCH_PER_DRAG: f32 = core::f32::consts::FRAC_PI_3;
202const CAMERA_YAW_SCALE: f32 = CAMERA_YAW_PER_DRAG / WINDOW_WIDTH as f32;
203const CAMERA_PITCH_SCALE: f32 = CAMERA_PITCH_PER_DRAG / WINDOW_HEIGHT as f32;
204/// The range the camera's tilt is held inside, in radians: short of
205/// looking flat along the ground or straight down, either of which would
206/// stop framing the figure.
207const CAMERA_PITCH_RANGE: Range<f32> = -0.4..0.9;
208
209/// The panel's controls, a key and what it does.
210const CONTROLS: [(&str, &str); 10] = [
211    ("mouse", "turns the camera"),
212    ("click", "locks the pointer"),
213    ("escape", "frees the pointer"),
214    ("wasd or arrows", "walk"),
215    ("left shift", "runs"),
216    ("f", "attacks, chains on a second press"),
217    ("space", "jumps"),
218    ("n", "dances while idle"),
219    ("e", "sits on the seat and stands back up"),
220    ("r", "starts a new elf"),
221];
222
223/// The UI's own text color, read over the ground and the sky both.
224const PANEL_TEXT_COLOR: egui::Color32 = egui::Color32::from_gray(230);
225/// How much dark a panel or a prompt's own backdrop puts behind its text.
226const PANEL_BACKDROP: u8 = 190;
227/// The panel's own inner margin, around its labels.
228const PANEL_PADDING: i8 = 8;
229/// The size a world-space prompt reads at, in logical points.
230const PROMPT_SIZE: f32 = 15.0;
231/// Height a world-space prompt is lifted over the point it names.
232const PROMPT_LIFT: f32 = 0.35;
233/// Margin a prompt's own backdrop keeps past its galley, in logical points.
234const PROMPT_PADDING: f32 = 4.0;
235
236meshes! { enum Shape { Plane, Cube, Elf, Butterfly } }
237
238/// The one sky this game draws, a gradient set each frame.
239#[derive(Catalog, Clone, Copy, Debug, Eq, Hash, PartialEq)]
240enum Sky {
241    Day,
242}
243
244impl Skyboxes for Sky {
245    fn build(&self, _assets: &Assets) -> SkyboxData {
246        match self {
247            Self::Day => SkyboxData::gradient(SKY_ZENITH, SKY_HORIZON, SKY_NADIR).lit_by(SKY_LIGHT),
248        }
249    }
250}
251
252#[derive(Catalog, Clone, PartialEq, Eq, Hash)]
253struct Elf;
254
255/// The clips `ElfState` and `ScrubbedState` play, named as the source
256/// names them.
257#[derive(Clip, Clone, Debug, PartialEq, Eq, Hash)]
258enum ElfClip {
259    #[clip("idle")]
260    Idle,
261    #[clip("walk")]
262    Walk,
263    #[clip("jog")]
264    Jog,
265    #[clip("attack")]
266    Attack,
267    #[clip("hit")]
268    Hit,
269    #[clip("death")]
270    Death,
271    #[clip("sit_down")]
272    SitDown,
273    #[clip("sit")]
274    Sit,
275    #[clip("stand_up")]
276    StandUp,
277    #[clip("jump")]
278    Jump,
279    #[clip("dance")]
280    Dance,
281}
282
283impl Mesh<NoParts, ElfClip> for Elf {
284    fn build(&self, assets: &Assets) -> MeshData<NoParts, ElfClip> {
285        assets.model(ELF_ROOT)
286    }
287}
288
289/// What the game fills each tick to move `ElfState` on.
290#[derive(Default)]
291struct ElfInput {
292    /// The elf's speed this tick, a fraction of [`ELF_SPEED`].
293    speed: f32,
294    attack: bool,
295    /// True the tick a hurt patch is stepped onto and [`FATAL_HITS`] have
296    /// not yet landed.
297    hit: bool,
298    /// True the tick a hurt patch is stepped onto the third time, which
299    /// [`FATAL_HITS`] counts.
300    dying: bool,
301    jump: bool,
302    /// True the tick `Jump`'s own height curve returns the elf to the
303    /// ground after it launches.
304    landed: bool,
305    dance: bool,
306    /// `Button::Interact`, read as sitting down, standing back up, or
307    /// nothing, by the state it reaches.
308    interact: bool,
309    /// Whether the elf stands close enough to the cube to sit on it.
310    near_seat: bool,
311}
312
313#[derive(Clone, Copy, Eq, PartialEq, Debug)]
314enum ElfState {
315    Idle,
316    Locomotion,
317    Attack,
318    Hit,
319    Death,
320    SitDown,
321    Sit,
322    StandUp,
323    Jump,
324    Dance,
325}
326
327impl ElfState {
328    /// Whether the elf is on the seat, or on its way onto or off it.
329    fn seated(self) -> bool {
330        matches!(self, Self::SitDown | Self::Sit | Self::StandUp)
331    }
332
333    /// `Locomotion` where `input` reads a walk or a run, `Idle` at rest:
334    /// where a grounded state returns once whatever interrupted it ends.
335    fn grounded(input: &ElfInput) -> Self {
336        match input.speed > WALK_THRESHOLD {
337            true => Self::Locomotion,
338            false => Self::Idle,
339        }
340    }
341}
342
343impl AnimationStates for ElfState {
344    type Clip = ElfClip;
345    type Input = ElfInput;
346
347    fn entry() -> Self {
348        Self::Idle
349    }
350
351    fn motion(&self, input: &ElfInput) -> Motion<ElfClip> {
352        match self {
353            Self::Idle => Motion::looping(ElfClip::Idle),
354            Self::Locomotion => {
355                Motion::blend(ElfClip::Walk, ElfClip::Jog, input.speed).paced(input.speed)
356            }
357            Self::Attack => Motion::once(ElfClip::Attack),
358            Self::Hit => Motion::once(ElfClip::Hit),
359            Self::Death => Motion::once(ElfClip::Death),
360            Self::SitDown => Motion::once(ElfClip::SitDown),
361            Self::Sit => Motion::looping(ElfClip::Sit),
362            Self::StandUp => Motion::once(ElfClip::StandUp),
363            Self::Jump => Motion::once(ElfClip::Jump),
364            Self::Dance => Motion::looping(ElfClip::Dance),
365        }
366    }
367
368    fn next(&self, input: &ElfInput, at: Progress) -> Option<Transition<Self>> {
369        match (self, input) {
370            (Self::Death, _) => None,
371            (_, ElfInput { dying: true, .. }) => Some(Self::Death.fade(DEATH_FADE)),
372            (_, ElfInput { hit: true, .. }) if *self != Self::Hit => {
373                Some(Self::Hit.fade(HIT_ENTER_FADE))
374            }
375            (Self::Hit, _) if at.ended() => Some(ElfState::grounded(input).fade(HIT_EXIT_FADE)),
376            (Self::Attack, ElfInput { attack: true, .. }) if at.past(ATTACK_RELEASE) => Some(
377                Self::Attack
378                    .restarted()
379                    .entering_at(ATTACK_CHAIN_ENTRY)
380                    .fade(ATTACK_CHAIN_FADE),
381            ),
382            (Self::Attack, _) if at.past(ATTACK_RELEASE) => {
383                Some(ElfState::grounded(input).fade(ATTACK_EXIT_FADE))
384            }
385            (Self::SitDown | Self::Sit | Self::StandUp, i) if i.speed > WALK_THRESHOLD => {
386                Some(Self::Locomotion.fade(STAND_EXIT_FADE))
387            }
388            (Self::SitDown | Self::Sit | Self::StandUp, ElfInput { attack: true, .. }) => {
389                Some(Self::Attack.fade(ATTACK_ENTER_FADE))
390            }
391            (Self::SitDown | Self::Sit | Self::StandUp, ElfInput { jump: true, .. }) => {
392                Some(Self::Jump.fade(JUMP_ENTER_FADE))
393            }
394            (Self::SitDown, _) if at.ended() => Some(Self::Sit.at_once()),
395            (Self::Sit, ElfInput { interact: true, .. }) => Some(Self::StandUp.fade(STAND_UP_FADE)),
396            (Self::StandUp, _) if at.ended() => Some(Self::Idle.fade(STAND_EXIT_FADE)),
397            (Self::Jump, ElfInput { landed: true, .. }) => {
398                Some(ElfState::grounded(input).fade(JUMP_EXIT_FADE))
399            }
400            (Self::Jump, _) if at.ended() => Some(ElfState::grounded(input).fade(JUMP_EXIT_FADE)),
401            (
402                Self::Idle | Self::Locomotion,
403                ElfInput {
404                    interact: true,
405                    near_seat: true,
406                    ..
407                },
408            ) => Some(Self::SitDown.fade(SIT_DOWN_FADE)),
409            (Self::Idle | Self::Locomotion, ElfInput { attack: true, .. }) => {
410                Some(Self::Attack.fade(ATTACK_ENTER_FADE))
411            }
412            (Self::Idle | Self::Locomotion, ElfInput { jump: true, .. }) => {
413                Some(Self::Jump.fade(JUMP_ENTER_FADE))
414            }
415            (Self::Idle, ElfInput { dance: true, .. }) => Some(Self::Dance.fade(DANCE_FADE)),
416            (Self::Dance, i) if i.speed > WALK_THRESHOLD => {
417                Some(Self::Locomotion.fade(IDLE_LOCOMOTION_FADE))
418            }
419            (Self::Idle, i) if i.speed > WALK_THRESHOLD => {
420                Some(Self::Locomotion.fade(IDLE_LOCOMOTION_FADE))
421            }
422            (Self::Locomotion, i) if i.speed <= WALK_THRESHOLD => {
423                Some(Self::Idle.fade(IDLE_LOCOMOTION_FADE))
424            }
425            _ => None,
426        }
427    }
428}
429
430/// A machine of one state, posed by nothing but the value it scrubs.
431#[derive(Clone, Copy, Eq, PartialEq, Debug)]
432enum ScrubbedState {
433    SitDown,
434}
435
436/// What the game fills each tick to move `ScrubbedState` on.
437#[derive(Default)]
438struct ScrubbedInput {
439    /// How far into sitting down the second elf reads, a fraction in
440    /// `0.0..=1.0`.
441    settled: f32,
442}
443
444impl AnimationStates for ScrubbedState {
445    type Clip = ElfClip;
446    type Input = ScrubbedInput;
447
448    fn entry() -> Self {
449        Self::SitDown
450    }
451
452    fn motion(&self, input: &ScrubbedInput) -> Motion<ElfClip> {
453        Motion::scrubbed(ElfClip::SitDown, input.settled)
454    }
455
456    fn next(&self, _input: &ScrubbedInput, _at: Progress) -> Option<Transition<Self>> {
457        None
458    }
459}
460
461#[derive(Catalog, Clone, PartialEq, Eq, Hash)]
462struct Butterfly;
463
464/// The one clip `FlyingState` plays, named as the source names it.
465#[derive(Clip, Clone, Debug, PartialEq, Eq, Hash)]
466enum ButterflyClip {
467    #[clip("fly")]
468    Fly,
469}
470
471impl Mesh<NoParts, ButterflyClip> for Butterfly {
472    fn build(&self, assets: &Assets) -> MeshData<NoParts, ButterflyClip> {
473        assets.model(BUTTERFLY_ROOT)
474    }
475}
476
477/// A machine of one state, looping the butterfly's only clip.
478#[derive(Clone, Copy, Eq, PartialEq, Debug)]
479enum FlyingState {
480    Flying,
481}
482
483impl AnimationStates for FlyingState {
484    type Clip = ButterflyClip;
485    type Input = ();
486
487    fn entry() -> Self {
488        Self::Flying
489    }
490
491    fn motion(&self, _input: &()) -> Motion<ButterflyClip> {
492        Motion::looping(ButterflyClip::Fly)
493    }
494
495    fn next(&self, _input: &(), _at: Progress) -> Option<Transition<Self>> {
496        None
497    }
498}
499
500/// The butterfly's position and the `yaw` it faces, `t` seconds into its
501/// closed loop around [`BUTTERFLY_CENTER`].
502fn butterfly_pose(t: f32) -> (Vec3, f32) {
503    let angle = t * BUTTERFLY_ANGULAR_SPEED;
504    let position = BUTTERFLY_CENTER
505        + Vec3::new(
506            BUTTERFLY_RADIUS.x * angle.cos(),
507            BUTTERFLY_HEIGHT,
508            BUTTERFLY_RADIUS.y * angle.sin(),
509        );
510    let direction = Vec3::new(
511        -BUTTERFLY_RADIUS.x * angle.sin(),
512        0.0,
513        BUTTERFLY_RADIUS.y * angle.cos(),
514    );
515    (position, direction.x.atan2(direction.z))
516}
517
518/// How far into sitting down the second elf reads at `distance` from the
519/// first.
520fn settled_at(distance: f32) -> f32 {
521    1.0 - (distance - SCRUB_NEAR) / (SCRUB_FAR - SCRUB_NEAR)
522}
523
524/// A camera [`CAMERA_BACK`] behind and [`CAMERA_UP`] above `target`, tilted
525/// `pitch` radians and turned `yaw` radians around it, looking at a point
526/// [`CAMERA_LOOK_HEIGHT`] above `target`.
527fn orbit_camera(target: Vec3, yaw: f32, pitch: f32) -> Camera {
528    let look_at = target + Vec3::Y * CAMERA_LOOK_HEIGHT;
529    let base = Vec3::new(0.0, CAMERA_UP, CAMERA_BACK);
530    let offset = Quat::from_rotation_y(yaw) * (Quat::from_rotation_x(pitch) * base);
531    Camera::new(
532        View::look_at(look_at + offset, look_at),
533        Projection::perspective(CAMERA_FOV),
534    )
535}
536
537/// The logical point egui paints the physical pixel `pixel` at.
538fn logical(pixel: Vec2, pixels_per_point: f32) -> egui::Pos2 {
539    let point = pixel / pixels_per_point;
540    egui::pos2(point.x, point.y)
541}
542
543#[derive(InputButtonAction, Clone, Copy)]
544enum Button {
545    Run,
546    Attack,
547    Jump,
548    Dance,
549    Interact,
550    Restart,
551    Hold,
552    Release,
553}
554
555impl InputButtonAction for Button {
556    fn bindings(&self) -> Vec<ButtonBinding> {
557        match self {
558            Button::Run => vec![Key::LeftShift.into()],
559            Button::Attack => vec![Key::F.into()],
560            Button::Jump => vec![Key::Space.into()],
561            Button::Dance => vec![Key::N.into()],
562            Button::Interact => vec![Key::E.into()],
563            Button::Restart => vec![Key::R.into()],
564            Button::Hold => vec![MouseButton::Left.into()],
565            Button::Release => vec![Key::Escape.into()],
566        }
567    }
568}
569
570/// The camera's own controls: turned and tilted by how far the pointer
571/// moves sideways and upward each tick.
572#[derive(InputAxisAction, Clone, Copy)]
573enum Axis {
574    CameraYaw,
575    CameraPitch,
576}
577
578impl InputAxisAction for Axis {
579    fn bindings(&self) -> Vec<AxisBinding> {
580        match self {
581            Axis::CameraYaw => {
582                vec![AxisBinding::pointer_delta(PointerDelta::Sideways).scale(CAMERA_YAW_SCALE)]
583            }
584            Axis::CameraPitch => {
585                vec![AxisBinding::pointer_delta(PointerDelta::Up).scale(CAMERA_PITCH_SCALE)]
586            }
587        }
588    }
589}
590
591#[derive(InputAxis2Action, Clone, Copy)]
592enum Move {
593    Walk,
594}
595
596impl InputAxis2Action for Move {
597    fn bindings(&self) -> Vec<Axis2Binding> {
598        match self {
599            Move::Walk => vec![
600                Axis2Binding::from(ButtonAxis2 {
601                    left: Key::A,
602                    right: Key::D,
603                    down: Key::S,
604                    up: Key::W,
605                }),
606                Axis2Binding::from(ButtonAxis2 {
607                    left: Key::Left,
608                    right: Key::Right,
609                    down: Key::Down,
610                    up: Key::Up,
611                }),
612            ],
613        }
614    }
615}
616
617struct Controls;
618
619impl InputActions for Controls {
620    type Button = Button;
621    type Axis = Axis;
622    type Axis2 = Move;
623}
624
625/// `text` in [`PANEL_TEXT_COLOR`].
626fn panel_text(text: impl Into<String>) -> egui::RichText {
627    egui::RichText::new(text.into()).color(PANEL_TEXT_COLOR)
628}
629
630struct Scene {
631    elf_pos: Vec3,
632    elf_prev: Vec3,
633    elf_yaw: f32,
634    /// Height the elf is lifted over the ground while off the ground,
635    /// integrated in [`Game::tick`] from [`Self::jump_speed`].
636    elf_height: f32,
637    elf_height_prev: f32,
638    /// The elf's own vertical speed while off the ground, in meters a
639    /// second, positive upward.
640    jump_speed: f32,
641    elf_input: ElfInput,
642    elf_animator: Animator<Elf, ElfState>,
643    scrubbed_animator: Animator<Elf, ScrubbedState>,
644    butterfly_animator: Animator<Butterfly, FlyingState>,
645    hits: u32,
646    /// True while a hurt patch already held the elf, so leaving and
647    /// returning to the same patch counts as a new hit.
648    in_patch: bool,
649    /// Whether the pointer is held; a click takes it, escape frees it.
650    holding: bool,
651    /// The camera's turn around the elf, and its tilt, both in radians.
652    camera_yaw: f32,
653    camera_pitch: f32,
654    /// The last state change the panel names.
655    last_event: &'static str,
656}
657
658impl Scene {
659    fn init(_ctx: &mut InitContext<'_, Scene>) -> Result<Self, Error> {
660        Ok(Self {
661            elf_pos: ELF_START,
662            elf_prev: ELF_START,
663            elf_yaw: 0.0,
664            elf_height: 0.0,
665            elf_height_prev: 0.0,
666            jump_speed: 0.0,
667            elf_input: ElfInput::default(),
668            elf_animator: Animator::new(),
669            scrubbed_animator: Animator::new(),
670            butterfly_animator: Animator::new(),
671            hits: 0,
672            in_patch: false,
673            holding: false,
674            camera_yaw: 0.0,
675            camera_pitch: 0.0,
676            last_event: "none yet",
677        })
678    }
679
680    /// Turns the camera by how far the pointer moves sideways and upward,
681    /// [`CAMERA_PITCH_RANGE`] holding how far it tilts.
682    fn steer_camera(&mut self, ctx: &mut FrameContext<'_, Scene>) {
683        self.camera_yaw -= ctx.axis(Axis::CameraYaw);
684        self.camera_pitch = (self.camera_pitch + ctx.axis(Axis::CameraPitch))
685            .clamp(CAMERA_PITCH_RANGE.start, CAMERA_PITCH_RANGE.end);
686    }
687
688    /// Turns `elf_yaw` toward the heading `ctx` reads, relative to the
689    /// camera's own turn, and moves `elf_pos` along it; the speed it moves
690    /// at, a fraction of [`ELF_SPEED`], held at [`WALK_CAP`] until
691    /// `Button::Run` is held.
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    }
707
708    /// Integrates [`Self::elf_height`] under [`GRAVITY`] from
709    /// [`Self::jump_speed`], held at the ground; `true` the tick it
710    /// returns there from above it.
711    fn fall(&mut self, dt: f32) -> bool {
712        let off_ground = self.elf_height > 0.0;
713        self.jump_speed -= GRAVITY * dt;
714        self.elf_height = (self.elf_height + self.jump_speed * dt).max(0.0);
715        if self.elf_height == 0.0 {
716            self.jump_speed = 0.0;
717        }
718        off_ground && self.elf_height == 0.0
719    }
720
721    /// The hurt patch `elf_pos` stands inside, if any.
722    fn patch_underfoot(&self) -> Option<Vec3> {
723        HURT_PATCHES
724            .into_iter()
725            .find(|&patch| self.elf_pos.distance(patch) < HURT_RADIUS)
726    }
727
728    /// Reads the controls and moves the elf, filling [`Self::elf_input`]
729    /// for [`ElfState`] to read.
730    fn tick_elf(&mut self, ctx: &mut TickContext<'_, Scene>) {
731        let grounded = matches!(
732            self.elf_animator.state(),
733            ElfState::Idle | ElfState::Locomotion
734        );
735
736        self.elf_input.speed = self.advance(ctx);
737        self.elf_input.attack = ctx.pressed(Button::Attack);
738        self.elf_input.jump = ctx.pressed(Button::Jump);
739        self.elf_input.dance = ctx.pressed(Button::Dance);
740
741        self.elf_input.near_seat = self.elf_pos.distance(SEAT_POSITION) < SEAT_INTERACT_RADIUS;
742        self.elf_input.interact = ctx.pressed(Button::Interact);
743        if self.elf_input.interact && grounded && self.elf_input.near_seat {
744            self.elf_pos = SEAT_SPOT;
745            self.elf_yaw = SEAT_FACING;
746        }
747
748        let underfoot = self.patch_underfoot();
749        let entered_patch = underfoot.is_some() && !self.in_patch;
750        self.in_patch = underfoot.is_some();
751        self.hits += u32::from(entered_patch);
752        self.elf_input.hit = entered_patch && self.hits < FATAL_HITS;
753        self.elf_input.dying = entered_patch && self.hits >= FATAL_HITS;
754        if entered_patch {
755            self.last_event = match self.elf_input.dying {
756                true => "elf died",
757                false => "elf hit",
758            };
759        }
760    }
761
762    /// Starts a new [`Animator`] over the elf's own state, its position and
763    /// hit count reset with it.
764    fn restart_elf(&mut self) {
765        self.elf_animator = Animator::new();
766        self.elf_pos = ELF_START;
767        self.elf_prev = ELF_START;
768        self.elf_yaw = 0.0;
769        self.elf_height = 0.0;
770        self.elf_height_prev = 0.0;
771        self.jump_speed = 0.0;
772        self.elf_input = ElfInput::default();
773        self.hits = 0;
774        self.in_patch = false;
775        self.last_event = "new elf started";
776    }
777
778    fn panel(&self, ctx: &mut FrameContext<'_, Scene>) {
779        let state = match self.elf_animator.state() {
780            ElfState::Idle => "idle",
781            ElfState::Locomotion if self.elf_input.speed > WALK_CAP => "running",
782            ElfState::Locomotion => "walking",
783            ElfState::Attack => "attacking",
784            ElfState::Hit => "hit",
785            ElfState::Death => "dead",
786            ElfState::SitDown => "sitting down",
787            ElfState::Sit => "sitting",
788            ElfState::StandUp => "standing up",
789            ElfState::Jump => "jumping",
790            ElfState::Dance => "dancing",
791        };
792        ctx.ui(|ui| {
793            egui::Frame::new()
794                .fill(egui::Color32::from_black_alpha(PANEL_BACKDROP))
795                .inner_margin(PANEL_PADDING)
796                .corner_radius(f32::from(PANEL_PADDING))
797                .show(ui, |ui| {
798                    ui.heading(panel_text(format!("elf is {state}")));
799                    ui.label(panel_text(format!(
800                        "hits taken {} of the {} red patches hurt for, {}",
801                        self.hits, FATAL_HITS, self.last_event
802                    )));
803                    ui.label(panel_text(match self.elf_animator.transitioning() {
804                        true => "fading between clips",
805                        false => "one clip playing",
806                    }));
807                    ui.add_space(f32::from(PANEL_PADDING));
808                    egui::Grid::new("controls").show(ui, |ui| {
809                        for (key, does) in CONTROLS {
810                            ui.label(panel_text(key));
811                            ui.label(panel_text(does));
812                            ui.end_row();
813                        }
814                    });
815                });
816        });
817    }
818
819    /// A prompt over the seat, each hurt patch, and the scrubbed elf,
820    /// naming what a player finds there; the seat's own prompt names the
821    /// live binding of `Button::Interact` by its own name, not one fixed
822    /// in the code, and is absent while the elf sits on it.
823    fn draw_prompts(&self, ctx: &mut FrameContext<'_, Scene>, camera: Camera) {
824        let sit_key = ctx
825            .bindings(Button::Interact)
826            .into_iter()
827            .next()
828            .map_or_else(|| "interact".to_owned(), |binding| binding.to_string());
829        let sit = ctx.text_layout(
830            &format!("{sit_key} sits"),
831            egui::FontId::proportional(PROMPT_SIZE),
832        );
833        let hurts = ctx.text_layout("hurts", egui::FontId::proportional(PROMPT_SIZE));
834        let walk_closer = ctx.text_layout("walk closer", egui::FontId::proportional(PROMPT_SIZE));
835
836        let mut prompts = vec![(
837            SCRUBBED_ELF_POSITION + Vec3::Y * (ELF_HEIGHT + PROMPT_LIFT),
838            walk_closer,
839        )];
840        if !self.elf_animator.state().seated() {
841            prompts.push((
842                SEAT_POSITION + Vec3::Y * (SEAT_HEAD_HEIGHT + PROMPT_LIFT),
843                sit,
844            ));
845        }
846        prompts.extend(HURT_PATCHES.map(|patch| (patch + Vec3::Y * PROMPT_LIFT, hurts.clone())));
847
848        let window_size = ctx.window_size();
849        let pixels_per_point = ctx.pixels_per_point();
850        ctx.ui(|ui| {
851            let painter = ui.painter();
852            for (point, galley) in prompts {
853                let Some(pixel) = camera.pixel_of(point, window_size) else {
854                    continue;
855                };
856                let at = logical(pixel, pixels_per_point);
857                let ink = galley.mesh_bounds;
858                let pos = egui::pos2(at.x - ink.center().x, at.y - ink.center().y);
859                let backdrop = egui::Rect::from_center_size(
860                    at,
861                    ink.size() + egui::Vec2::splat(PROMPT_PADDING * 2.0),
862                );
863                painter.rect_filled(
864                    backdrop,
865                    PROMPT_PADDING,
866                    egui::Color32::from_black_alpha(PANEL_BACKDROP),
867                );
868                painter.galley(pos, galley, PANEL_TEXT_COLOR);
869            }
870        });
871    }
872}
873
874impl Game for Scene {
875    type Meshes = Shape;
876    type Sounds = NoSounds;
877    type InputActions = Controls;
878    type Skyboxes = Sky;
879    type SurfaceStyles = NoSurfaceStyles;
880    type PostEffects = NoPostEffects;
881
882    fn tick(&mut self, ctx: &mut TickContext<'_, Scene>) {
883        self.elf_prev = self.elf_pos;
884        self.elf_height_prev = self.elf_height;
885
886        if ctx.pressed(Button::Restart) {
887            self.restart_elf();
888        }
889        if ctx.pressed(Button::Hold) {
890            self.holding = true;
891        }
892        if ctx.pressed(Button::Release) {
893            self.holding = false;
894        }
895
896        self.elf_input.landed = self.fall(ctx.dt().as_secs_f32());
897        self.tick_elf(ctx);
898        ctx.animate(Elf, &mut self.elf_animator, &self.elf_input);
899
900        if self.elf_animator.entered(ElfState::Jump) {
901            self.jump_speed = JUMP_LAUNCH_SPEED;
902        }
903        if self.elf_animator.left(ElfState::StandUp) {
904            self.last_event = "elf stood up";
905        }
906        if self.elf_animator.entered(ElfState::Sit) {
907            self.last_event = "elf sat down";
908        }
909        if self.elf_animator.entered(ElfState::Death) {
910            self.last_event = "elf died";
911        }
912
913        let scrubbed_input = ScrubbedInput {
914            settled: settled_at(self.elf_pos.distance(SCRUBBED_ELF_POSITION)),
915        };
916        ctx.animate(Elf, &mut self.scrubbed_animator, &scrubbed_input);
917        ctx.animate(Butterfly, &mut self.butterfly_animator, &());
918    }
919
920    fn frame(&mut self, ctx: &mut FrameContext<'_, Scene>) {
921        self.steer_camera(ctx);
922
923        let alpha = ctx.alpha();
924        let elf_pos = self.elf_prev.lerp(self.elf_pos, alpha);
925        let elf_height = self.elf_height_prev + (self.elf_height - self.elf_height_prev) * alpha;
926        let (butterfly_pos, butterfly_yaw) = butterfly_pose(ctx.elapsed().as_secs_f32());
927
928        let camera = orbit_camera(elf_pos, self.camera_yaw, self.camera_pitch);
929        ctx.set_camera(camera);
930        ctx.set_cursor(if self.holding {
931            Cursor::Held
932        } else {
933            Cursor::Arrow
934        });
935        ctx.set_skybox(Sky::Day);
936        ctx.set_exposure(3.0);
937        ctx.set_bloom(0.2);
938        ctx.light(Light::directional(SUN_DIRECTION, SUN_COLOR).shadow());
939        ctx.light(
940            Light::point(
941                LAMP_POST_POSITION + Vec3::Y * (LAMP_POST_HEIGHT + LAMP_HEAD_GAP * 0.5),
942                LAMP_LIGHT_COLOR,
943                LAMP_LIGHT_RANGE,
944            )
945            .shadow(),
946        );
947        ctx.light(
948            Light::spot(Spot {
949                position: SPOT_POSITION,
950                direction: SPOT_DIRECTION,
951                color: SPOT_COLOR,
952                range: SPOT_RANGE,
953                angle: SPOT_ANGLE,
954            })
955            .shadow(),
956        );
957        ctx.light(
958            Light::point(butterfly_pos, BUTTERFLY_LIGHT_COLOR, BUTTERFLY_LIGHT_RANGE).shadow(),
959        );
960
961        ctx.draw(
962            Plane
963                .at(Transform::from_scale(Vec3::new(
964                    GROUND_SIZE,
965                    1.0,
966                    GROUND_SIZE,
967                )))
968                .material(Material::lit(GROUND_COLOR)),
969        );
970        for patch in HURT_PATCHES {
971            ctx.draw(
972                Plane
973                    .at(Transform::from_scale_rotation_translation(
974                        Vec3::splat(HURT_RADIUS * 2.0),
975                        Quat::IDENTITY,
976                        patch,
977                    ))
978                    .material(Material::lit(HURT_COLOR)),
979            );
980        }
981        ctx.draw(
982            Cube.at(Transform::from_scale_rotation_translation(
983                Vec3::new(SEAT_FOOTPRINT, SEAT_HEIGHT, SEAT_FOOTPRINT),
984                Quat::IDENTITY,
985                SEAT_POSITION + Vec3::Y * SEAT_HEIGHT * 0.5,
986            ))
987            .material(Material::lit(SEAT_COLOR)),
988        );
989        ctx.draw(
990            Cube.at(Transform::from_scale_rotation_translation(
991                Vec3::new(LAMP_POST_THICKNESS, LAMP_POST_HEIGHT, LAMP_POST_THICKNESS),
992                Quat::IDENTITY,
993                LAMP_POST_POSITION + Vec3::Y * LAMP_POST_HEIGHT * 0.5,
994            ))
995            .material(Material::lit(LAMP_POST_COLOR)),
996        );
997        ctx.draw(
998            Cube.at(Transform::from_scale_rotation_translation(
999                Vec3::splat(LAMP_HEAD_SIZE),
1000                Quat::IDENTITY,
1001                LAMP_POST_POSITION
1002                    + Vec3::Y * (LAMP_POST_HEIGHT + LAMP_HEAD_GAP + LAMP_HEAD_SIZE * 0.5),
1003            ))
1004            .material(Material::color(Color::BLACK).emissive(LAMP_LIGHT_COLOR)),
1005        );
1006        ctx.draw(
1007            Cube.at(Transform::from_scale_rotation_translation(
1008                Vec3::splat(SPOT_FIXTURE_SIZE),
1009                Quat::IDENTITY,
1010                SPOT_POSITION + Vec3::Y * SPOT_FIXTURE_SIZE * 0.5,
1011            ))
1012            .material(Material::lit(SPOT_FIXTURE_COLOR)),
1013        );
1014
1015        ctx.draw(
1016            Elf.at(Transform::from_rotation_translation(
1017                Quat::from_rotation_y(self.elf_yaw),
1018                elf_pos + Vec3::Y * elf_height,
1019            ))
1020            .posed(&self.elf_animator),
1021        );
1022        ctx.draw(
1023            Elf.at(Transform::from_rotation_translation(
1024                Quat::from_rotation_y(core::f32::consts::PI),
1025                SCRUBBED_ELF_POSITION,
1026            ))
1027            .posed(&self.scrubbed_animator),
1028        );
1029        ctx.draw(
1030            Butterfly
1031                .at(Transform::from_rotation_translation(
1032                    Quat::from_rotation_y(butterfly_yaw),
1033                    butterfly_pos,
1034                ))
1035                .posed(&self.butterfly_animator)
1036                .material(Material::lit(Color::WHITE).emissive(BUTTERFLY_EMISSIVE)),
1037        );
1038
1039        self.draw_prompts(ctx, camera);
1040        self.panel(ctx);
1041    }
More examples
Hide additional examples
examples/sprite-adventure.rs (line 152)
152const PLAYER_SPAWN: Vec3 = Vec3::new(0.0, 0.0, 5.0);
153const CAVE_MOUTH: Vec3 = Vec3::new(0.0, 0.0, -6.0);
154/// Landing position for a return from the cave: past the [`ENTRANCE`] band,
155/// so the return does not count as another step through the mouth.
156const RETURN_SPAWN: Vec3 = Vec3::new(
157    CAVE_MOUTH.x,
158    0.0,
159    CAVE_MOUTH.z + MOUTH_CROSSING_INSET + PORTAL_CLEARANCE,
160);
161
162/// The pond's position and half the side of it: the shoreline sprite is
163/// three meters square, the sixteen texels to the meter the game keeps. The
164/// styled water is drawn barely off the ground, clear of z-fighting with it; the
165/// shore draws at the ground's own `y` instead, submitted after it, so
166/// submission order alone separates the two and the shore never shadows the
167/// ground it lies on.
168const POND_CENTER: Vec3 = Vec3::new(4.2, 0.01, -1.5);
169const POND_HALF: f32 = 1.5;
170/// Half the open water that sprite's shoreline rings — the middle three
171/// fifths of its side, both drawn styled and blocking the player: the square
172/// stops well inside the shore, so its own edges never show.
173const POND_WATER_HALF: f32 = POND_HALF * 0.6;
174const WATER_COLOR: Color = Color::rgba(0.06, 0.20, 0.27, 0.92);
175const WATER_LITNESS: f32 = 0.45;
176
177/// Footprint and turn of the two crates beside the spawn: far enough apart
178/// that neither turned box overlaps the other, and clear of the path's verge.
179const CRATE_POSITIONS: [(f32, f32, f32); 2] = [(-2.7, 3.5, 0.5), (-1.2, 2.6, -0.3)];
180const CRATE_SIZE: f32 = 1.0;
181const WELL_POSITION: Vec3 = Vec3::new(2.6, 0.0, 3.2);
182/// The well's rim: width, height, depth in meters, as wide as the two tiles
183/// its mouth is drawn from.
184const WELL_SIZE: Vec3 = Vec3::new(2.0, 0.6, 2.0);
185/// Mouth lift over the rim's top face, clear of z-fighting.
186const WELL_MOUTH_LIFT: f32 = 0.01;
187
188/// Flora position: `x, z`, and whether it is a rock rather than a bush.
189const FLORA: [(f32, f32, bool); 6] = [
190    (-4.2, 1.4, false),
191    (-4.8, -1.8, true),
192    (5.8, 2.6, false),
193    (6.6, -1.0, false),
194    (3.2, -4.8, true),
195    (-2.2, -5.0, true),
196];
197/// Both sprites are two tiles wide — the tileset's own sixteen texels to
198/// the meter, as the ground and player. Height matches each one's own
199/// cropped sheet, so its visible size stays the same.
200const BUSH_WIDTH: f32 = 2.0;
201const BUSH_HEIGHT: f32 = 1.9375;
202const ROCK_WIDTH: f32 = 2.0;
203const ROCK_HEIGHT: f32 = 1.875;
204/// Each one's block: the base under the sprite, well inside the two meters
205/// it is drawn across — a bush's stems, a rock's foot.
206const BUSH_FOOTPRINT: f32 = 0.6;
207const ROCK_FOOTPRINT: f32 = 0.8;
208
209/// The bush rows that ring the clearing, hiding where the ground tiles stop:
210/// an inner and an outer half-extent in meters, the step along each side,
211/// and the gap left either side of the path.
212const HEDGE_INNER_HALF: f32 = 10.4;
213const HEDGE_OUTER_HALF: f32 = 11.8;
214const HEDGE_STEP: f32 = 1.4;
215const HEDGE_GATE_HALF: f32 = 2.2;
216
217/// Walkable extent of the clearing: to the inner hedge row's near face, so
218/// the bushes the player can see are the wall.
219const CLEARING_HALF: f32 = HEDGE_INNER_HALF - BUSH_WIDTH * 0.5;
220
221/// The mouth's flanking pillars, half this far either side of its center,
222/// and the dark opening drawn between them.
223const MOUTH_PILLAR_OFFSET: f32 = 1.1;
224/// Width, height and depth of each of those pillars: the whole stone sheet
225/// at its own texels to the meter, so the capital lands where a capital
226/// goes.
227const MOUTH_PILLAR_SIZE: Vec3 = Vec3::new(1.0, STONE_ROWS as f32 * TILE_SIZE, 1.0);
228/// Whole tiles the stone across the pillars runs: enough to end
229/// past both of them, so no face of it lands in either pillar, and a
230/// whole number so its masonry repeats without a part course.
231const MOUTH_LINTEL_TILES: f32 = 4.0;
232/// That stone's own size: those tiles across, one course tall, and deeper
233/// than the pillars.
234const MOUTH_LINTEL_SIZE: Vec3 = Vec3::new(
235    MOUTH_LINTEL_TILES * TILE_SIZE,
236    TILE_SIZE,
237    MOUTH_PILLAR_SIZE.z + 0.1,
238);
239/// Height of the dark between the pillars, drawn up into the lintel, so a
240/// camera looking down finds stone over it rather than the ground behind.
241const MOUTH_DARK_HEIGHT: f32 = MOUTH_PILLAR_SIZE.y + MOUTH_LINTEL_SIZE.y * 0.5;
242/// Half the clear opening between the pillars' inner faces.
243const MOUTH_OPENING_HALF: f32 = MOUTH_PILLAR_OFFSET - MOUTH_PILLAR_SIZE.x * 0.5;
244/// Distance in front of a mouth for the plane that stepping through its
245/// opening crosses.
246const MOUTH_CROSSING_INSET: f32 = 0.1;
247/// Distance past that plane a portal lands the player.
248const PORTAL_CLEARANCE: f32 = 0.8;
249
250/// The overworld's mouth into the cave, and the one at the cave's near end
251/// that leads back out.
252const ENTRANCE: Mouth = Mouth {
253    at: CAVE_MOUTH,
254    room_side: 1.0,
255};
256const EXIT: Mouth = Mouth {
257    at: CAVE_EXIT,
258    room_side: -1.0,
259};
260
261const SUN_COLOR: Color = Color::rgb(0.92, 0.87, 0.72);
262/// The sun's direction: low enough that props cast shadows about their own
263/// length, and from over the player's left shoulder, which is where the
264/// tileset's own sprites are shaded from.
265const SUN_DIRECTION: Vec3 = Vec3::new(0.6, -0.75, 0.4);
266
267// ---------------------------------------------------------------------
268// Cave layout
269// ---------------------------------------------------------------------
270
271const CAVE_HALF_WIDTH: f32 = 4.0;
272/// Room tiling and walls toward the camera, past the wall closing it: under
273/// the bottom corners of the frame, wherever the player walks.
274const CAVE_NEAR_Z: f32 = 10.0;
275const CAVE_FAR_Z: f32 = -6.0;
276/// Walkable extent of the room at its far end: the back wall's inner face,
277/// less the player's own half-width, so the sprite stops flush against
278/// stone.
279const CAVE_WALK_FAR_Z: f32 = CAVE_FAR_Z + TILE_SIZE * 0.5 + WALKER_WIDTH * 0.5;
280/// Walkable extent at the room's near end: past the plane stepping out
281/// crosses, so the last step through the mouth is never clamped away. The
282/// near wall itself is what keeps the player from the ledge beyond.
283const CAVE_WALK_NEAR_Z: f32 = CAVE_EXIT.z + PLAYER_RADIUS;
284/// As many meters as the wall face is tiles tall, so it samples the sheet
285/// at the floor's own texels to the meter.
286const WALL_HEIGHT: f32 = 3.0;
287/// Wall height: over the camera, so it neither looks over them nor down onto
288/// their tops.
289const WALL_TOP: f32 = CAVE_CAMERA_OFFSET.y + TILE_SIZE * 0.5;
290/// Courses that takes; the last one is cut to what is left of it.
291const WALL_COURSES: i32 = (WALL_TOP / WALL_HEIGHT) as i32 + 1;
292/// The tile row the room's near end is closed along, from each side wall to
293/// the mouth in it.
294const CAVE_LIP_Z: f32 = 5.0;
295/// Height of that wall: one tile, so its top face is at the floor's own
296/// texels to the meter, and low enough that it never hides the player.
297const CAVE_LIP_HEIGHT: f32 = TILE_SIZE;
298/// The cave's own mouth position: in the near wall, its pillars half their
299/// depth past it.
300const CAVE_EXIT: Vec3 = Vec3::new(0.0, 0.0, CAVE_LIP_Z + MOUTH_PILLAR_SIZE.z * 0.5);
301/// Landing position for an entry into the cave: past the [`EXIT`] band, so
302/// the entry does not count as another step through the mouth.
303const CAVE_SPAWN: Vec3 = Vec3::new(
304    CAVE_EXIT.x,
305    0.0,
306    CAVE_EXIT.z - MOUTH_CROSSING_INSET - PORTAL_CLEARANCE,
307);
308
309const DOOR_Z: f32 = 0.0;
310/// The near face of the wall the door hangs in: past it the wall is
311/// between the camera and the player, and is drawn through.
312const DOOR_WALL_NEAR_Z: f32 = DOOR_Z + TILE_SIZE * 0.5;
313/// Alpha for that wall and its door once the player has been behind them
314/// for [`GHOST_RAMP_TICKS`]: opaque enough for stone in a dark room,
315/// translucent enough for the chamber and the player to show through.
316const GHOST_ALPHA: f32 = 0.6;
317/// The fade of a draw nothing is seen through.
318const SOLID: f32 = 1.0;
319/// Ticks the wall and door take to fade between [`SOLID`] and
320/// [`GHOST_ALPHA`].
321const GHOST_RAMP_TICKS: u32 = 6;
322/// Span either side of the player the wall is drawn through: wide enough for
323/// the sight line to the player, no wider, so no more of the light the
324/// stone holds off the chamber passes through it than that line needs.
325const GHOST_CORRIDOR_HALF: f32 = 1.5;
326/// The door's hinge: `tools/keep_fixture.py`'s box is hinged at its own
327/// local origin and spans [`DOOR_WIDTH`] along local `+X`.
328const DOOR_HINGE: Vec3 = Vec3::new(-DOOR_WIDTH * 0.5, 0.0, DOOR_Z);
329/// Width of that box, as the fixture builds it.
330const DOOR_WIDTH: f32 = 1.0;
331/// Thickness of it, as the fixture builds it.
332const DOOR_THICKNESS: f32 = 0.12;
333/// Height of it, as the fixture builds it: the stone over the doorway starts
334/// here.
335const DOOR_HEIGHT: f32 = 1.9;
336/// Half the doorway the door hangs in: one tile wide, on the room's axis.
337const DOORWAY_HALF: f32 = TILE_SIZE * 0.5;
338/// Span from that axis of the wall the door hangs in: to the side walls'
339/// inner faces.
340const DOOR_WALL_END: f32 = CAVE_HALF_WIDTH + TILE_SIZE * 0.5;
341const INTERACT_POINT: Vec3 = Vec3::new(0.0, 0.0, DOOR_Z);
342const INTERACT_RADIUS: f32 = 1.8;
343
344/// The door slab's own color, a deep red the cave's grey stone never is, so
345/// the slab reads as a door rather than more wall.
346const DOOR_COLOR: Color = Color::rgb(0.58, 0.16, 0.09);
347const DOOR_LITNESS: f32 = 0.85;
348/// Thickness of the posts and lintel framing the doorway, and how far in
349/// front of the wall face they stand, clear of z-fighting with it.
350const DOOR_FRAME_THICKNESS: f32 = 0.14;
351const DOOR_FRAME_STANDOFF: f32 = 0.03;
352/// The frame's own color, visible from the cave's own spawn well before a
353/// torch reaches the doorway.
354const DOOR_FRAME_COLOR: Color = Color::rgb(0.85, 0.55, 0.2);
355const DOOR_FRAME_LITNESS: f32 = 0.9;
356/// The frame's own light while the door is closed and within
357/// [`INTERACT_RADIUS`]: the cue that it can be opened.
358const DOOR_FRAME_GLOW: Color = Color::rgb(1.6, 0.9, 0.35);
359
360const GEM_POSITION: Vec3 = Vec3::new(0.0, 0.5, -4.6);
361const GEM_BOB_HEIGHT: f32 = 0.12;
362const GEM_SPIN_SPEED: f32 = 1.4;
363const PICKUP_RADIUS: f32 = 1.0;
364const GEM_COLOR: Color = Color::rgb(0.35, 0.95, 0.85);
365/// The gem's own light: what lights the chamber until the door opens on the
366/// torches, and gone with the gem once it is taken.
367const GEM_LIGHT_COLOR: Color = Color::rgb(0.3, 0.85, 0.78);
368/// Reach of it: short of the door wall, so what the wall casts never depends
369/// on the gem.
370const GEM_LIGHT_RANGE: f32 = 4.0;
371/// Lift over the gem, clear of the gem's own facets, which would otherwise
372/// shadow the chamber from inside it.
373const GEM_LIGHT_LIFT: f32 = 0.7;
374
375/// The two torches' position, `x, z`: flanking the doorway on the near side
376/// of the wall, the side the player arrives on.
377const TORCH_POSITIONS: [(f32, f32); 2] = [(-3.0, 0.6), (3.0, 0.6)];
378const TORCH_STAND_HEIGHT: f32 = 2.0;
379/// Thickness of a torch's post: the four texels the sprite draws it as,
380/// which is also what it blocks the player as.
381const TORCH_STAND_WIDTH: f32 = 0.25;
382/// Width of the sprite around that post: its canvas is twice the post,
383/// transparent either side.
384const TORCH_SPRITE_WIDTH: f32 = TORCH_STAND_WIDTH * 2.0;
385const TORCH_LIGHT_RANGE: f32 = 10.0;
386/// Height above its flame a torch's light is placed, and how far it is
387/// offset from the post toward the room: straight over the post, its own face
388/// turns edge-on to the light and goes dark.
389const TORCH_LIGHT_LIFT: f32 = 0.7;
390const TORCH_LIGHT_STANDOFF: f32 = 0.8;
391const TORCH_LIGHT_COLOR: Color = Color::rgb(1.0, 0.6, 0.28);
392/// Size of one cell of the flame's loop: a tile, as everything else the
393/// tilesets draw.
394const FLAME_SIZE: f32 = TILE_SIZE;
395/// Lift of the flame's center over the post's top: its own half-height, less
396/// the overlap that keeps the two from parting.
397const FLAME_LIFT: f32 = FLAME_SIZE * 0.5 - 0.1;
398/// The flame's tint, past `1.0`: an additive draw's tint scales its sampled
399/// texel, so this lifts the flame's own lit texels out of the cave's dark
400/// without a flat color added over its dark, unlit base.
401const FLAME_TINT: Color = Color::rgb(2.2, 1.5, 0.7);
402/// Speed the loop runs, in cells a second.
403const FLAME_RATE: f32 = 12.0;
404/// Speed the flicker runs, and how far it lifts and rolls the flame.
405const FLAME_FLICKER_SPEED: f32 = 9.0;
406const FLAME_BOB: f32 = 0.03;
407const FLAME_ROLL: f32 = 0.12;
408
409/// How much dark the overlay puts behind its lines, and how far that dark
410/// extends past them — the ground under it is bright enough to hide bare text
411/// without it.
412const HUD_BACKDROP: u8 = 200;
413const HUD_PADDING: i8 = 8;
414
415/// Ticks the door's own world prompt still reads as it swings once opened,
416/// after which it is taken as open and the prompt drops.
417const DOOR_SWING_TICKS: u32 = 24;
418/// Size the door's world prompt reads at, in logical points, and how far
419/// over the doorway it is lifted.
420const DOOR_PROMPT_SIZE: f32 = 15.0;
421const DOOR_PROMPT_LIFT: f32 = 0.3;
422/// Margin the prompt's own backdrop keeps past its galley, in logical
423/// points, and how much dark that backdrop puts behind the text.
424const DOOR_PROMPT_PADDING: f32 = 4.0;
425const DOOR_PROMPT_BACKDROP: u8 = 190;
426const DOOR_PROMPT_COLOR: egui::Color32 = egui::Color32::from_gray(230);
427
428const CAMERA_FOV: f32 = 45.0;
429/// Both cameras look down about forty degrees: shallow enough that the
430/// upright sprites keep close to their full height on screen.
431const OVERWORLD_CAMERA_OFFSET: Vec3 = Vec3::new(0.0, 9.0, 11.0);
432const CAVE_CAMERA_OFFSET: Vec3 = Vec3::new(0.0, 6.5, 7.5);
433
434// ---------------------------------------------------------------------
435// Collision footprints
436// ---------------------------------------------------------------------
437
438/// Excess reach of a square box turned `turn` about `+Y`, along either
439/// ground axis, as a factor of its side.
440fn turned_span(turn: f32) -> f32 {
441    turn.cos().abs() + turn.sin().abs()
442}
443
444/// One drawn prop's block: its footprint in the `x, z` the player walks.
445///
446/// Everything drawn on the ground is one — a box at its own extents,
447/// an upright sprite at the base under it. Pickups, flames, and whatever a
448/// clamp already holds — the hedgerow, the cave's own walls — are not.
449#[derive(Clone, Copy)]
450struct Obstacle {
451    center: Vec2,
452    half: Vec2,
453}
454
455impl Obstacle {
456    /// The footprint of a box `size` across centered on `at`, both
457    /// in the `x, z` the player walks.
458    fn footprint(at: Vec2, size: Vec2) -> Self {
459        Self {
460            center: at,
461            half: size * 0.5,
462        }
463    }
464
465    /// The footprint that just covers `corners` — what a prop the game turns
466    /// blocks the player as.
467    fn over(corners: [Vec2; 4]) -> Self {
468        let [first, rest @ ..] = corners;
469        let low = rest.iter().fold(first, |low, &corner| low.min(corner));
470        let high = rest.iter().fold(first, |high, &corner| high.max(corner));
471
472        Self {
473            center: (low + high) * 0.5,
474            half: (high - low) * 0.5,
475        }
476    }
477
478    /// Result of pushing a circle of `radius` at `point` out of this obstacle
479    /// the shortest way; `point` itself when it is already clear.
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    }
1721
1722    fn frame_overworld(&mut self, ctx: &mut FrameContext<'_, Keep>) {
1723        let drawn_at = self.previous.lerp(self.position, ctx.alpha());
1724        ctx.set_camera(Self::camera(drawn_at, OVERWORLD_CAMERA_OFFSET));
1725        ctx.light(Light::directional(SUN_DIRECTION, SUN_COLOR).shadow());
1726
1727        self.draw_ground(ctx);
1728        self.draw_hedgerow(ctx);
1729        self.draw_pond(ctx);
1730        self.draw_crates(ctx);
1731        self.draw_well(ctx);
1732        self.draw_flora(ctx);
1733        Self::draw_mouth(ctx, ENTRANCE);
1734        self.draw_walker(ctx, drawn_at);
1735    }
1736
1737    fn frame_cave(&mut self, ctx: &mut FrameContext<'_, Keep>) {
1738        let drawn_at = self.previous.lerp(self.position, ctx.alpha());
1739        let camera = Self::camera(drawn_at, CAVE_CAMERA_OFFSET);
1740        ctx.set_camera(camera);
1741
1742        self.draw_cave_floor(ctx);
1743        self.draw_cave_walls(ctx);
1744        Self::draw_door_wall(ctx, (self.ghost > 0.0).then_some(drawn_at.x), self.ghost);
1745        Self::draw_mouth(ctx, EXIT);
1746        self.draw_torches(ctx);
1747        self.draw_door(ctx, self.ghost);
1748        self.draw_door_frame(ctx, self.ghost);
1749        if !self.gem_taken {
1750            self.draw_gem(ctx);
1751        }
1752        self.draw_walker(ctx, drawn_at);
1753        self.draw_door_prompt(ctx, camera);
1754    }
1755
1756    /// Instructions and the door's interact hint — gathered before `ctx.ui`,
1757    /// which cannot read `ctx`.
1758    fn overlay(&mut self, ctx: &mut FrameContext<'_, Keep>) {
1759        let near_door = self.area == Area::Cave
1760            && !self.door_opening
1761            && self.position.distance(INTERACT_POINT) < INTERACT_RADIUS;
1762        let gem_taken = self.area == Area::Cave && self.gem_taken;
1763        let mut reset_clicked = false;
1764
1765        ctx.ui(|ui| {
1766            egui::Frame::new()
1767                .fill(egui::Color32::from_black_alpha(HUD_BACKDROP))
1768                .inner_margin(HUD_PADDING)
1769                .corner_radius(f32::from(HUD_PADDING))
1770                .show(ui, |ui| {
1771                    ui.visuals_mut().override_text_color = Some(egui::Color32::WHITE);
1772                    ui.label("wasd / arrows / stick to walk");
1773                    if near_door {
1774                        ui.label("e / west button to open the door");
1775                    }
1776                    if gem_taken {
1777                        ui.label("gem recovered");
1778                    }
1779                    ui.label("kept between runs: position, gem, cave");
1780                    ui.label("r to reset world");
1781                    if ui.button("reset world").clicked() {
1782                        reset_clicked = true;
1783                    }
1784                });
1785        });
1786
1787        if reset_clicked {
1788            self.reset_requested = true;
1789        }
1790    }
1791}
1792
1793impl Game for Keep {
1794    type Meshes = Shape;
1795    type Sounds = Sound;
1796    type InputActions = Controls;
1797    type Skyboxes = NoSkyboxes;
1798    type SurfaceStyles = Looks;
1799    type PostEffects = NoPostEffects;
1800
1801    fn tick(&mut self, ctx: &mut TickContext<'_, Keep>) {
1802        self.previous = self.position;
1803
1804        if self.reset_requested || ctx.pressed(Button::Reset) {
1805            self.reset_requested = false;
1806            self.reset(ctx);
1807            return;
1808        }
1809
1810        let heading = if ctx.ui_wants_keyboard() {
1811            Vec2::ZERO
1812        } else {
1813            ctx.axis2(Move::Walk)
1814        };
1815        match Facing::from_heading(heading) {
1816            Some(facing) => {
1817                self.facing = facing;
1818                self.walk_ticks += 1;
1819            }
1820            None => self.walk_ticks = 0,
1821        }
1822        let stride = Vec3::new(heading.x, 0.0, -heading.y) * WALK_SPEED * ctx.dt().as_secs_f32();
1823        self.position += stride;
1824        self.simulated += ctx.dt();
1825
1826        match self.area {
1827            Area::Overworld => self.tick_overworld(ctx),
1828            Area::Cave => self.tick_cave(ctx),
1829        }
1830    }
examples/ui-fonts.rs (line 42)
42const STATION_SIZE: Vec3 = Vec3::new(0.6, 0.9, 0.5);
43/// The emissive cube set against the `STATION_SIZE` cube's own `-Z` side.
44const STATION_FRONT_SIZE: Vec3 = Vec3::new(0.42, 0.5, 0.06);
45/// The distance past the `STATION_SIZE` cube's own face the emissive
46/// cube's front keeps, in meters, so the two stay flush at a shallow
47/// angle.
48const STATION_FRONT_OUTWARD: f32 = 0.005;
49
50const RADAR_SWEEP_RATE: f32 = 40.0;
51const REACTOR_RATE: f32 = 0.5;
52const REACTOR_BASE: f32 = 55.0;
53const REACTOR_SWING: f32 = 35.0;
54
55const SUN_DIRECTION: Vec3 = Vec3::new(0.4, -1.0, -0.3);
56const SUN_COLOR: Color = Color::rgb(0.6, 0.62, 0.7);
57
58const CAMERA_FOV: f32 = 42.0;
59const CAMERA_TARGET: Vec3 = Vec3::new(0.0, STATION_SIZE.y * 0.5, 0.0);
60const START_YAW: f32 = 0.4;
61const START_PITCH: f32 = 0.35;
62/// How far short of straight up or down the pitch may turn, in radians.
63const PITCH_LIMIT: f32 = 0.9;
64const START_DISTANCE: f32 = 8.0;
65const MIN_DISTANCE: f32 = 3.0;
66const MAX_DISTANCE: f32 = 14.0;
67/// Radians the camera turns by on its own, per second.
68const AUTO_TURN_RATE: f32 = 0.12;
69/// Radians the pointer's own motion turns the view by, per physical pixel
70/// it crosses, while [`Trigger::Hail`] is held.
71const TURN_SENSITIVITY: f32 = core::f32::consts::FRAC_PI_2 / 1280.0;
72/// The factor one full wheel step divides the distance to the target by.
73const ZOOM_STEP: f32 = 1.12;
74
75const BRACKET_MARGIN: f32 = 10.0;
76const BRACKET_STROKE: f32 = 2.0;
77const BRACKET_CORNER: f32 = 7.0;
78/// The gap kept past the line above's own full line height, so a glyph
79/// whose `mesh_bounds` reaches past that height still clears the line
80/// below.
81const STACK_GAP: f32 = 4.0;
82
83/// The gap between a `StationKind`'s own top and the `Prompt` drawn above
84/// it, in logical points.
85const PROMPT_LIFT: f32 = 20.0;
86
87const DIALOGUE_PADDING_X: f32 = 28.0;
88const DIALOGUE_PADDING_Y: f32 = 20.0;
89const DIALOGUE_MARGIN: f32 = 24.0;
90
91/// A sample the pixel font holds no glyph for, so `Proportional` falls
92/// back to egui's own font and `pixel-only` shows the missing glyph box.
93const FALLBACK_SAMPLE: &str = "café λ";
94/// Different widths of glyph together, so a family's own advance shows.
95const FAMILY_SAMPLE: &str = "mill and wall";
96const GRID_SAMPLE: &str = "the quick fox";
97
98meshes! { enum Shape { Cube, Plane } }
99
100/// This game's own sky: a dark, dim gradient, so each `StationKind`'s own
101/// emissive glow still reads as the platform's brightest color.
102#[derive(Catalog, Clone, Copy, Debug, PartialEq, Eq, Hash)]
103enum Sky {
104    Dusk,
105}
106
107impl Skyboxes for Sky {
108    fn build(&self, _assets: &Assets) -> SkyboxData {
109        SkyboxData::gradient(
110            Color::rgb(0.05, 0.06, 0.12),
111            Color::rgb(0.18, 0.12, 0.16),
112            Color::rgb(0.01, 0.01, 0.02),
113        )
114        .lit_by(0.25)
115    }
116}
117
118/// What one `StationKind` is drawn and named as.
119struct StationLook {
120    name: &'static str,
121    color: Color,
122    glow: Color,
123    lines: [&'static str; 2],
124}
125
126#[derive(Clone, Copy, PartialEq, Eq)]
127enum StationKind {
128    Radar,
129    Reactor,
130    Clock,
131}
132
133impl StationKind {
134    const ALL: [Self; 3] = [Self::Radar, Self::Reactor, Self::Clock];
135
136    fn index(self) -> usize {
137        self as usize
138    }
139
140    fn look(self) -> StationLook {
141        match self {
142            Self::Radar => StationLook {
143                name: "radar",
144                color: Color::rgb(0.22, 0.26, 0.30),
145                glow: Color::rgb(0.3, 1.4, 1.1),
146                lines: [
147                    "the radar sweeps the dark past the platform for anything that moves",
148                    "nothing answers back tonight",
149                ],
150            },
151            Self::Reactor => StationLook {
152                name: "reactor",
153                color: Color::rgb(0.30, 0.22, 0.18),
154                glow: Color::rgb(1.6, 0.7, 0.2),
155                lines: [
156                    "the reactor gauge holds steady at a comfortable idle",
157                    "plenty of power left for the long watch ahead",
158                ],
159            },
160            Self::Clock => StationLook {
161                name: "clock",
162                color: Color::rgb(0.20, 0.24, 0.22),
163                glow: Color::rgb(0.8, 0.9, 1.6),
164                lines: [
165                    "the clock keeps the same count it always has",
166                    "the watch ends when it says so and not before",
167                ],
168            },
169        }
170    }
171
172    /// This `StationKind`'s own center, on the platform's edge.
173    fn center(self) -> Vec3 {
174        let angle = self.index() as f32 / Self::ALL.len() as f32 * core::f32::consts::TAU;
175        Vec3::new(
176            STATION_RADIUS * angle.cos(),
177            STATION_SIZE.y * 0.5,
178            STATION_RADIUS * angle.sin(),
179        )
180    }
181
182    fn aabb(self) -> (Vec3, Vec3) {
183        let half = STATION_SIZE * 0.5;
184        (self.center() - half, self.center() + half)
185    }
186
187    /// The reading a `bracket` shows through `Monospace`, and the one
188    /// large number it shows through the `display` family, both from
189    /// `elapsed` seconds.
190    fn reading(self, elapsed: f32) -> (String, String) {
191        match self {
192            Self::Radar => {
193                let bearing = (elapsed * RADAR_SWEEP_RATE).rem_euclid(360.0);
194                (
195                    format!("{bearing:.0} degrees bearing"),
196                    format!("{bearing:03.0}"),
197                )
198            }
199            Self::Reactor => {
200                let percent = REACTOR_BASE + REACTOR_SWING * (elapsed * REACTOR_RATE).sin();
201                (
202                    format!("{percent:.0} percent output"),
203                    format!("{percent:.0}%"),
204                )
205            }
206            Self::Clock => {
207                let seconds = elapsed.rem_euclid(60.0);
208                (
209                    format!("{seconds:.1} seconds this minute"),
210                    format!("{seconds:04.1}"),
211                )
212            }
213        }
214    }
215}
216
217/// The `StationKind` `ray` lies nearest along, if any.
218fn hit_station(ray: Ray) -> Option<StationKind> {
219    StationKind::ALL
220        .into_iter()
221        .filter_map(|station| {
222            let (min, max) = station.aabb();
223            ray.hit_aabb(min, max).map(|distance| (distance, station))
224        })
225        .min_by(|(a, _), (b, _)| a.total_cmp(b))
226        .map(|(_, station)| station)
227}
228
229/// The logical point egui paints `pixel`, a physical pixel, at.
230fn logical(pixel: Vec2, pixels_per_point: f32) -> egui::Pos2 {
231    let point = pixel / pixels_per_point;
232    egui::pos2(point.x, point.y)
233}
234
235/// `text` at `font`, in this game's own text color.
236fn styled(text: impl Into<String>, font: egui::FontId) -> egui::RichText {
237    egui::RichText::new(text.into())
238        .font(font)
239        .color(TEXT_COLOR)
240}
241
242/// The position that draws `galley` with its `mesh_bounds` centered on
243/// `center_x` and its `mesh_bounds`'s own bottom at `bottom`.
244fn ink_bottom_at(galley: &egui::Galley, center_x: f32, bottom: f32) -> egui::Pos2 {
245    let ink = galley.mesh_bounds;
246    egui::pos2(center_x - ink.center().x, bottom - ink.max.y)
247}
248
249/// How a control reads to a player: a glyph the `prompts` family draws,
250/// or a control's own name in the `Proportional` font.
251enum Prompt {
252    Glyph(char),
253    Text(String),
254}
255
256impl Prompt {
257    fn text(&self) -> String {
258        match self {
259            Self::Glyph(glyph) => glyph.to_string(),
260            Self::Text(text) => text.clone(),
261        }
262    }
263
264    fn family(&self) -> egui::FontFamily {
265        match self {
266            Self::Glyph(_) => egui::FontFamily::Name(PROMPT_FAMILY.into()),
267            Self::Text(_) => egui::FontFamily::Proportional,
268        }
269    }
270}
271
272/// `binding` read for a player: a mouse glyph for its left or right
273/// button, or its own `Display` text otherwise, so a key reads as `E`
274/// and a pad button as its name.
275fn prompt(binding: &ButtonBinding) -> Prompt {
276    match binding {
277        ButtonBinding::Mouse(MouseButton::Left) => Prompt::Glyph('\u{E0EC}'),
278        ButtonBinding::Mouse(MouseButton::Right) => Prompt::Glyph('\u{E0F0}'),
279        other => Prompt::Text(other.to_string()),
280    }
281}
282
283/// A name in `corners`, a reading and a number, placed one above the
284/// other upward from `at`, which sits right above a `StationKind`'s top,
285/// one line height clear of it: the name boxed and nearest `at`, the
286/// reading a full line height above it, the number a full line height
287/// above that, so no two lines and no line and the box ever overlap.
288///
289/// Each gap is measured in the line below's own full line height, not its
290/// smaller `mesh_bounds`, so a glyph whose `mesh_bounds` reaches past that
291/// height still clears the line above it. The name's own frame is sized to
292/// its `mesh_bounds`, margin included, and the name centers inside it on
293/// every side. The caller measures every `Galley`; this only draws. Lifts
294/// into another game unchanged.
295fn bracket(
296    painter: &egui::Painter,
297    at: egui::Pos2,
298    name: Arc<egui::Galley>,
299    reading: Arc<egui::Galley>,
300    number: Arc<egui::Galley>,
301) {
302    let frame_bottom = at.y - name.rect.height();
303    let name_ink = name.mesh_bounds;
304    let frame_height = name_ink.height() + BRACKET_MARGIN * 2.0;
305    let frame = egui::Rect::from_min_size(
306        egui::pos2(
307            at.x - name_ink.width() * 0.5 - BRACKET_MARGIN,
308            frame_bottom - frame_height,
309        ),
310        egui::vec2(name_ink.width() + BRACKET_MARGIN * 2.0, frame_height),
311    );
312    let name_pos = ink_bottom_at(&name, at.x, frame_bottom - BRACKET_MARGIN);
313
314    let reading_bottom = frame.top() - STACK_GAP;
315    let reading_pos = ink_bottom_at(&reading, at.x, reading_bottom);
316
317    let number_bottom = reading_pos.y - reading.rect.height() - STACK_GAP;
318    let number_pos = ink_bottom_at(&number, at.x, number_bottom);
319
320    corners(painter, frame);
321    painter.galley(name_pos, name, TEXT_COLOR);
322    painter.galley(reading_pos, reading, TEXT_COLOR);
323    painter.galley(number_pos, number, TEXT_COLOR);
324}
325
326/// `glyph`, its `mesh_bounds` centered on `at`. Lifts into another game
327/// unchanged.
328fn prompt_at(painter: &egui::Painter, at: egui::Pos2, glyph: Arc<egui::Galley>) {
329    let ink = glyph.mesh_bounds;
330    let pos = egui::pos2(at.x - ink.center().x, at.y - ink.center().y);
331    painter.galley(pos, glyph, TEXT_COLOR);
332}
333
334/// Four short lines at `rect`'s own corners, in place of its sides.
335fn corners(painter: &egui::Painter, rect: egui::Rect) {
336    let stroke = egui::Stroke::new(BRACKET_STROKE, TEXT_COLOR);
337    for (corner, inward) in [
338        (rect.left_top(), egui::vec2(1.0, 1.0)),
339        (rect.right_top(), egui::vec2(-1.0, 1.0)),
340        (rect.left_bottom(), egui::vec2(1.0, -1.0)),
341        (rect.right_bottom(), egui::vec2(-1.0, -1.0)),
342    ] {
343        painter.line_segment(
344            [corner, corner + egui::vec2(inward.x * BRACKET_CORNER, 0.0)],
345            stroke,
346        );
347        painter.line_segment(
348            [corner, corner + egui::vec2(0.0, inward.y * BRACKET_CORNER)],
349            stroke,
350        );
351    }
352}
353
354/// A name shown large and one line typed a glyph a tick, over two lines a
355/// click steps through. Holds no `StationKind` of its own, so it lifts
356/// into another game unchanged.
357struct Dialogue {
358    name: String,
359    lines: [String; 2],
360    line: usize,
361    revealed: usize,
362}
363
364impl Dialogue {
365    fn start(name: impl Into<String>, lines: [String; 2]) -> Self {
366        Self {
367            name: name.into(),
368            lines,
369            line: 0,
370            revealed: 0,
371        }
372    }
373
374    fn current_line(&self) -> &str {
375        &self.lines[self.line]
376    }
377
378    /// Shows one more glyph of the current line, up to its whole length.
379    fn tick(&mut self) {
380        let len = self.current_line().chars().count();
381        self.revealed = (self.revealed + 1).min(len);
382    }
383
384    /// Steps to the next line; `false` where the last line was already
385    /// shown, for the caller to close instead.
386    fn advance(&mut self) -> bool {
387        if self.line + 1 < self.lines.len() {
388            self.line += 1;
389            self.revealed = 0;
390            true
391        } else {
392            false
393        }
394    }
395
396    /// Draws the box at a size `whole_line` set, so it never grows as the
397    /// glyphs of the same line arrive: `set_min_size` inside the window
398    /// holds that size from the frame this window first draws, rather
399    /// than egui's own resize state, which only grows a window to match
400    /// what the frame before it drew.
401    fn draw(&self, ui: &mut egui::Ui, whole_line: egui::Vec2) {
402        let display = egui::FontFamily::Name(DISPLAY_FAMILY.into());
403        let box_size = egui::vec2(
404            whole_line.x + DIALOGUE_PADDING_X,
405            whole_line.y + HEADING_SIZE + DIALOGUE_PADDING_Y,
406        );
407        egui::Window::new("hail")
408            .title_bar(false)
409            .resizable(false)
410            .collapsible(false)
411            .anchor(
412                egui::Align2::CENTER_BOTTOM,
413                egui::vec2(0.0, -DIALOGUE_MARGIN),
414            )
415            .show(ui.ctx(), |ui| {
416                ui.set_min_size(box_size);
417                ui.label(styled(&self.name, egui::FontId::new(HEADING_SIZE, display)));
418                let shown: String = self.current_line().chars().take(self.revealed).collect();
419                ui.label(styled(shown, egui::FontId::proportional(BODY_SIZE)));
420            });
421    }
422}
423
424/// Every family this game loaded, its name at `16` points beside a sample
425/// at `32` in that family; the same sample under `Proportional`, where
426/// egui's own fonts back what the pixel font holds no glyph for, beside
427/// it again under a family that holds only the pixel font; and the pixel
428/// font at `16` points beside itself at `17`, where its own grid stops
429/// holding it crisp.
430fn sheet(ui: &mut egui::Ui) {
431    egui::Window::new("font sheet")
432        .anchor(egui::Align2::CENTER_CENTER, egui::Vec2::ZERO)
433        .collapsible(false)
434        .resizable(false)
435        .show(ui.ctx(), |ui| {
436            egui::Grid::new("font sheet grid").show(ui, |ui| {
437                for (label, family) in [
438                    ("proportional", egui::FontFamily::Proportional),
439                    ("monospace", egui::FontFamily::Monospace),
440                    ("display", egui::FontFamily::Name(DISPLAY_FAMILY.into())),
441                ] {
442                    ui.label(styled(label, egui::FontId::proportional(BODY_SIZE)));
443                    ui.label(styled(
444                        FAMILY_SAMPLE,
445                        egui::FontId::new(HEADING_SIZE, family),
446                    ));
447                    ui.end_row();
448                }
449
450                let pixel_only = egui::FontFamily::Name(PIXEL_ONLY_FAMILY.into());
451                for (label, family) in [
452                    ("egui's fonts behind", egui::FontFamily::Proportional),
453                    ("pixel font alone", pixel_only),
454                ] {
455                    ui.label(styled(label, egui::FontId::proportional(BODY_SIZE)));
456                    ui.label(styled(
457                        FALLBACK_SAMPLE,
458                        egui::FontId::new(BODY_SIZE, family),
459                    ));
460                    ui.end_row();
461                }
462
463                for (label, size) in [("16 points", 16.0), ("17 points", 17.0)] {
464                    ui.label(styled(label, egui::FontId::proportional(BODY_SIZE)));
465                    ui.label(styled(GRID_SAMPLE, egui::FontId::proportional(size)));
466                    ui.end_row();
467                }
468            });
469        });
470}
471
472#[derive(InputButtonAction, Clone, Copy)]
473enum Trigger {
474    Hail,
475    Sheet,
476    Close,
477}
478
479impl InputButtonAction for Trigger {
480    fn bindings(&self) -> Vec<ButtonBinding> {
481        match self {
482            Self::Hail => vec![MouseButton::Left.into()],
483            Self::Sheet => vec![Key::Tab.into()],
484            Self::Close => vec![Key::Escape.into()],
485        }
486    }
487}
488
489/// The pointer's own motion, read only while [`Trigger::Hail`] is held.
490#[derive(InputAxis2Action, Clone, Copy)]
491enum Turn {
492    Look,
493}
494
495impl InputAxis2Action for Turn {
496    fn bindings(&self) -> Vec<Axis2Binding> {
497        match self {
498            Self::Look => vec![Axis2Binding::pointer().scale(TURN_SENSITIVITY)],
499        }
500    }
501}
502
503#[derive(InputAxisAction, Clone, Copy)]
504enum Zoom {
505    Wheel,
506}
507
508impl InputAxisAction for Zoom {
509    fn bindings(&self) -> Vec<AxisBinding> {
510        match self {
511            Self::Wheel => vec![AxisBinding::from(WheelDelta::Up).scale(4.0)],
512        }
513    }
514}
515
516struct Controls;
517
518impl InputActions for Controls {
519    type Button = Trigger;
520    type Axis = Zoom;
521    type Axis2 = Turn;
522}
523
524/// Reads every name in `names` and adds them under `family`, front to
525/// back in the order given, ahead of whatever `family` already held.
526fn install_family(
527    fonts: &mut egui::FontDefinitions,
528    startup: &mut Startup,
529    family: egui::FontFamily,
530    names: &[&str],
531) -> Result<(), Error> {
532    for &name in names {
533        if !fonts.font_data.contains_key(name) {
534            fonts
535                .font_data
536                .insert(name.to_owned(), startup.font(name)?.into());
537        }
538    }
539    let list = fonts.families.entry(family).or_default();
540    for &name in names.iter().rev() {
541        list.insert(0, name.to_owned());
542    }
543    Ok(())
544}
545
546/// The camera around [`CAMERA_TARGET`], its own distance clamped between
547/// [`MIN_DISTANCE`] and [`MAX_DISTANCE`], `yaw` free and `pitch` held to
548/// [`PITCH_LIMIT`]. Holds nothing of the game, so it copies into another
549/// one with its own values.
550struct Orbit {
551    yaw: f32,
552    pitch: f32,
553    distance: f32,
554}
555
556impl Default for Orbit {
557    fn default() -> Self {
558        Self {
559            yaw: START_YAW,
560            pitch: START_PITCH,
561            distance: START_DISTANCE,
562        }
563    }
564}
565
566impl Orbit {
567    fn camera(&self) -> Camera {
568        let direction = Vec3::new(
569            self.pitch.cos() * self.yaw.sin(),
570            self.pitch.sin(),
571            self.pitch.cos() * self.yaw.cos(),
572        );
573        Camera::new(
574            View::look_at(CAMERA_TARGET + direction * self.distance, CAMERA_TARGET),
575            Projection::perspective(CAMERA_FOV),
576        )
577    }
578
579    /// Turns `yaw` by `-by.x` and `pitch` by `by.y`, `pitch` held to its
580    /// limit.
581    fn turn(&mut self, by: Vec2) {
582        self.yaw -= by.x;
583        self.pitch = (self.pitch + by.y).clamp(-PITCH_LIMIT, PITCH_LIMIT);
584    }
585
586    /// Divides the distance to [`CAMERA_TARGET`] by `factor`, held to its
587    /// own least and most.
588    fn zoom(&mut self, factor: f32) {
589        self.distance = (self.distance / factor).clamp(MIN_DISTANCE, MAX_DISTANCE);
590    }
591}
592
593struct WatchRoom {
594    elapsed: Duration,
595    orbit: Orbit,
596    hailed: Option<StationKind>,
597    dialogue: Option<Dialogue>,
598    sheet_open: bool,
599}
600
601impl WatchRoom {
602    fn init(ctx: &mut InitContext<'_, Self>) -> Result<Self, Error> {
603        let startup = ctx.startup();
604        let mut fonts = egui::FontDefinitions::default();
605        for (family, names) in [
606            (egui::FontFamily::Proportional, &[PROPORTIONAL_FONT][..]),
607            (egui::FontFamily::Monospace, &[MONOSPACE_FONT][..]),
608            (
609                egui::FontFamily::Name(DISPLAY_FAMILY.into()),
610                &[DISPLAY_FONT][..],
611            ),
612            (
613                egui::FontFamily::Name(PIXEL_ONLY_FAMILY.into()),
614                &[PROPORTIONAL_FONT][..],
615            ),
616            (
617                egui::FontFamily::Name(PROMPT_FAMILY.into()),
618                &[PROMPT_FONT, PROPORTIONAL_FONT][..],
619            ),
620        ] {
621            install_family(&mut fonts, startup, family, names)?;
622        }
623        startup.set_fonts(fonts)?;
624
625        Ok(Self {
626            elapsed: Duration::ZERO,
627            orbit: Orbit::default(),
628            hailed: None,
629            dialogue: None,
630            sheet_open: false,
631        })
632    }
633
634    fn handle_hail(&mut self, ctx: &mut TickContext<'_, Self>) {
635        let ray = ctx
636            .last_camera()
637            .ray_through(ctx.pointer(), ctx.window_size());
638        let Some(station) = hit_station(ray) else {
639            return;
640        };
641
642        if self.hailed != Some(station) {
643            self.hailed = Some(station);
644            let look = station.look();
645            self.dialogue = Some(Dialogue::start(look.name, look.lines.map(str::to_owned)));
646            return;
647        }
648        let Some(dialogue) = &mut self.dialogue else {
649            return;
650        };
651        if !dialogue.advance() {
652            self.dialogue = None;
653            self.hailed = None;
654        }
655    }
656
657    fn draw_station(&self, ctx: &mut FrameContext<'_, Self>, station: StationKind) {
658        let look = station.look();
659        let center = station.center();
660        let front_offset =
661            STATION_SIZE.z * 0.5 - STATION_FRONT_SIZE.z * 0.5 + STATION_FRONT_OUTWARD;
662        let front = center - Vec3::new(0.0, 0.0, front_offset);
663        for (size, position, material) in [
664            (STATION_SIZE, center, Material::lit(look.color)),
665            (
666                STATION_FRONT_SIZE,
667                front,
668                Material::color(Color::BLACK).emissive(look.glow),
669            ),
670        ] {
671            ctx.draw(
672                Cube.at(Transform::from_scale_rotation_translation(
673                    size,
674                    Quat::IDENTITY,
675                    position,
676                ))
677                .material(material),
678            );
679        }
680    }
681
682    fn draw_bracket(&self, ctx: &mut FrameContext<'_, Self>, camera: Camera, station: StationKind) {
683        let top = station.center() + Vec3::Y * (STATION_SIZE.y * 0.5);
684        let window_size = ctx.window_size();
685        let Some(pixel) = camera.pixel_of(top, window_size) else {
686            return;
687        };
688        let at = logical(pixel, ctx.pixels_per_point());
689
690        let name = ctx.text_layout(station.look().name, egui::FontId::proportional(BODY_SIZE));
691        let (reading_text, number_text) = station.reading(self.elapsed.as_secs_f32());
692        let reading = ctx.text_layout(&reading_text, egui::FontId::monospace(BODY_SIZE));
693        let number = ctx.text_layout(
694            &number_text,
695            egui::FontId::new(NUMBER_SIZE, egui::FontFamily::Name(DISPLAY_FAMILY.into())),
696        );
697
698        ctx.ui(|ui| bracket(ui.painter(), at, name, reading, number));
699    }
700
701    /// A `Prompt` for `Trigger::Hail`, above every `StationKind` but
702    /// `hovered`: what a player presses to reach one, apart from a hover.
703    fn draw_prompts(
704        &self,
705        ctx: &mut FrameContext<'_, Self>,
706        camera: Camera,
707        hovered: Option<StationKind>,
708    ) {
709        let Some(binding) = ctx.bindings(Trigger::Hail).into_iter().next() else {
710            return;
711        };
712        let hint = prompt(&binding);
713        let glyph = ctx.text_layout(&hint.text(), egui::FontId::new(PROMPT_SIZE, hint.family()));
714        let window_size = ctx.window_size();
715        let pixels_per_point = ctx.pixels_per_point();
716
717        ctx.ui(|ui| {
718            let painter = ui.painter();
719            for station in StationKind::ALL {
720                if Some(station) == hovered {
721                    continue;
722                }
723                let top = station.center() + Vec3::Y * (STATION_SIZE.y * 0.5);
724                let Some(pixel) = camera.pixel_of(top, window_size) else {
725                    continue;
726                };
727                let at = logical(pixel, pixels_per_point);
728                let at = egui::pos2(at.x, at.y - PROMPT_LIFT);
729                prompt_at(painter, at, glyph.clone());
730            }
731        });
732    }
733
734    /// The title, a line and the reading, each in a font this game loaded
735    /// rather than egui's own.
736    fn panel(&self, ctx: &mut FrameContext<'_, Self>) {
737        ctx.ui(|ui| {
738            ui.label(styled(
739                "a game's own fonts",
740                egui::FontId::proportional(HEADING_SIZE),
741            ));
742            ui.label(styled(
743                "drawn in Pixel Operator, the game's proportional font",
744                egui::FontId::proportional(BODY_SIZE),
745            ));
746            ui.label(styled(
747                "the readings above each station in Pixel Operator Mono",
748                egui::FontId::monospace(BODY_SIZE),
749            ));
750        });
751    }
752
753    fn draw_dialogue(&self, ctx: &mut FrameContext<'_, Self>) {
754        let Some(dialogue) = &self.dialogue else {
755            return;
756        };
757        let whole = ctx.text_layout(
758            dialogue.current_line(),
759            egui::FontId::proportional(BODY_SIZE),
760        );
761        let size = whole.size();
762        ctx.ui(|ui| dialogue.draw(ui, size));
763    }
764
765    /// The `StationKind` under the pointer, `None` while the UI holds it.
766    fn hovered(ctx: &FrameContext<'_, Self>) -> Option<StationKind> {
767        if ctx.ui_wants_pointer() {
768            return None;
769        }
770        hit_station(
771            ctx.last_camera()
772                .ray_through(ctx.pointer(), ctx.window_size()),
773        )
774    }
775
776    /// A held [`Trigger::Hail`] turns the camera by the pointer's own
777    /// motion; the wheel zooms it.
778    fn steer(&mut self, ctx: &mut FrameContext<'_, Self>) {
779        if !ctx.ui_wants_pointer() && ctx.down(Trigger::Hail) {
780            self.orbit.turn(ctx.axis2(Turn::Look));
781        }
782        let wheel = ctx.axis(Zoom::Wheel);
783        if !ctx.ui_wants_pointer() && wheel != 0.0 {
784            self.orbit.zoom(ZOOM_STEP.powf(wheel));
785        }
786    }
787}
788
789impl Game for WatchRoom {
790    type Meshes = Shape;
791    type Sounds = NoSounds;
792    type InputActions = Controls;
793    type Skyboxes = Sky;
794    type SurfaceStyles = NoSurfaceStyles;
795    type PostEffects = NoPostEffects;
796
797    fn tick(&mut self, ctx: &mut TickContext<'_, Self>) {
798        self.elapsed += ctx.dt();
799        self.orbit.yaw += AUTO_TURN_RATE * ctx.dt().as_secs_f32();
800
801        if let Some(dialogue) = &mut self.dialogue {
802            dialogue.tick();
803        }
804        if ctx.pressed(Trigger::Close) {
805            self.dialogue = None;
806            self.hailed = None;
807        }
808        if ctx.pressed(Trigger::Sheet) {
809            self.sheet_open = !self.sheet_open;
810        }
811        if ctx.pressed(Trigger::Hail) && !ctx.ui_wants_pointer() {
812            self.handle_hail(ctx);
813        }
814    }
815
816    fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
817        self.steer(ctx);
818
819        let camera = self.orbit.camera();
820        ctx.set_camera(camera);
821        ctx.set_skybox(Sky::Dusk);
822        ctx.light(Light::directional(SUN_DIRECTION, SUN_COLOR).shadow());
823
824        ctx.draw(
825            Plane
826                .at(Transform::from_scale(Vec3::new(
827                    PLATFORM_SIZE,
828                    1.0,
829                    PLATFORM_SIZE,
830                )))
831                .material(Material::lit(PLATFORM_COLOR)),
832        );
833        for station in StationKind::ALL {
834            self.draw_station(ctx, station);
835        }
836
837        let hovered = Self::hovered(ctx);
838        if !self.sheet_open {
839            if let Some(station) = hovered {
840                ctx.set_cursor(Cursor::Pointer);
841                self.draw_bracket(ctx, camera, station);
842            }
843            self.draw_prompts(ctx, camera, hovered);
844        }
845        if self.dialogue.is_some() {
846            self.draw_dialogue(ctx);
847        }
848        if self.sheet_open {
849            ctx.ui(sheet);
850        }
851        self.panel(ctx);
852    }
examples/post-effects.rs (line 13)
13const GLOW_POSITION: Vec3 = Vec3::new(0.0, 0.6, 0.0);
14const GLOW_SIZE: f32 = 0.9;
15const GLOW_COLOR: Color = Color::rgb(4.0, 2.2, 0.6);
16
17/// A sphere either side of the glowing cube, and its radius.
18const SPHERE_POSITIONS: [Vec3; 2] = [Vec3::new(-1.6, 0.5, 0.4), Vec3::new(1.6, 0.5, -0.4)];
19const SPHERE_SUBDIVISIONS: u32 = 3;
20
21const SUN_DIRECTION: Vec3 = Vec3::new(0.5, -1.0, -0.3);
22const SUN_COLOR: Color = Color::rgb(0.85, 0.8, 0.7);
23
24const GROUND_COLOR: Color = Color::rgb(0.16, 0.17, 0.15);
25const SPHERE_COLOR: Color = Color::rgb(0.5, 0.52, 0.55);
26
27/// Bloom the frame draws at, so [`GLOW_COLOR`] past `1.0` scatters.
28const SCENE_BLOOM: f32 = 0.5;
29
30meshes! { enum Shape { Plane, Cube, Sphere } }
31
32/// The values the WGSL `Vignette` reads: how far it darkens toward the
33/// frame's corners.
34#[derive(ShaderValues)]
35struct Vignette {
36    strength: f32,
37}
38
39impl PostEffect for Vignette {
40    const STAGE: EffectStage = EffectStage::ToneMapped;
41    const SHADER: &'static str = include_str!("post_effects_vignette.wgsl");
42}
43
44/// The values the WGSL `Grain` reads: how much grain it draws, and the
45/// seed its integer-hash is drawn from.
46#[derive(ShaderValues)]
47struct Grain {
48    strength: f32,
49    seed: u32,
50}
51
52impl PostEffect for Grain {
53    const STAGE: EffectStage = EffectStage::ToneMapped;
54    const SHADER: &'static str = include_str!("post_effects_grain.wgsl");
55}
56
57/// The values the WGSL `Scanlines` reads: how far it darkens every other
58/// pixel row.
59#[derive(ShaderValues)]
60struct Scanlines {
61    strength: f32,
62}
63
64impl PostEffect for Scanlines {
65    const STAGE: EffectStage = EffectStage::ToneMapped;
66    const SHADER: &'static str = include_str!("post_effects_scanlines.wgsl");
67}
68
69post_effects! { enum Look { Vignette, Grain, Scanlines } }
70
71struct PostEffectEffects {
72    ticks: u32,
73    vignette: f32,
74    grain: f32,
75    scanlines: f32,
76}
77
78impl PostEffectEffects {
79    fn init(_ctx: &mut InitContext<'_, Self>) -> Result<Self, Error> {
80        Ok(Self {
81            ticks: 0,
82            vignette: 0.5,
83            grain: 0.08,
84            scanlines: 0.3,
85        })
86    }
87
88    /// The scene every frame draws: a sun over a ground plane, a glowing
89    /// cube bloom scatters from, and a sphere either side of it.
90    fn draw_scene(&self, ctx: &mut FrameContext<'_, Self>) {
91        ctx.light(Light::directional(SUN_DIRECTION, SUN_COLOR).shadow());
92
93        ctx.draw(
94            Plane
95                .at(Transform::from_scale(Vec3::new(
96                    GROUND_SIZE,
97                    1.0,
98                    GROUND_SIZE,
99                )))
100                .material(Material::lit(GROUND_COLOR)),
101        );
102        ctx.draw(
103            Cube.at(Transform::from_scale_rotation_translation(
104                Vec3::splat(GLOW_SIZE),
105                Quat::IDENTITY,
106                GLOW_POSITION,
107            ))
108            .material(Material::color(Color::BLACK).emissive(GLOW_COLOR)),
109        );
110        for position in SPHERE_POSITIONS {
111            ctx.draw(
112                Sphere {
113                    subdivisions: SPHERE_SUBDIVISIONS,
114                }
115                .at(position)
116                .material(Material::lit(SPHERE_COLOR)),
117            );
118        }
119    }
120
121    /// A slider per effect, and the effects themselves run at the values
122    /// they hold — `Grain`'s seed is [`Self::ticks`], so the frame it
123    /// draws stays repeatable.
124    fn panel(&mut self, ctx: &mut FrameContext<'_, Self>) {
125        ctx.ui(|ui| {
126            ui.add(egui::Slider::new(&mut self.vignette, 0.0..=1.0).text("vignette"));
127            ui.add(egui::Slider::new(&mut self.grain, 0.0..=1.0).text("grain"));
128            ui.add(egui::Slider::new(&mut self.scanlines, 0.0..=1.0).text("scanlines"));
129        });
130
131        ctx.set_post_effect(Vignette {
132            strength: self.vignette,
133        });
134        ctx.set_post_effect(Grain {
135            strength: self.grain,
136            seed: self.ticks,
137        });
138        ctx.set_post_effect(Scanlines {
139            strength: self.scanlines,
140        });
141    }
142}
143
144impl Game for PostEffectEffects {
145    type Meshes = Shape;
146    type Sounds = NoSounds;
147    type InputActions = Key;
148    type Skyboxes = NoSkyboxes;
149    type SurfaceStyles = NoSurfaceStyles;
150    type PostEffects = Look;
151
152    fn tick(&mut self, _ctx: &mut TickContext<'_, Self>) {
153        self.ticks += 1;
154    }
155
156    fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
157        ctx.set_camera(Camera::new(
158            View::look_at(Vec3::new(0.0, 3.4, 4.6), Vec3::new(0.0, 0.2, 0.0)),
159            Projection::perspective(45.0),
160        ));
161        ctx.set_bloom(SCENE_BLOOM);
162
163        self.draw_scene(ctx);
164        self.panel(ctx);
165    }
examples/material-playground.rs (line 45)
45const FRONT_POSITION: Vec3 = Vec3::new(0.0, 0.7, -1.6);
46const FRONT_SCALE: f32 = 1.4;
47
48/// Sphere density every built sphere in this scene shares.
49const SPHERE_SUBDIVISIONS: u32 = 3;
50
51/// How many draws the reflection row makes, and the meters between their
52/// centers.
53const REFLECT_ROW_COUNT: usize = 5;
54const REFLECT_ROW_SPACING: f32 = 1.5;
55const REFLECT_ROW_Z: f32 = -1.6;
56const REFLECT_ROW_RADIUS: f32 = 0.55;
57
58const GROUND_COLOR: Color = Color::rgb(0.24, 0.25, 0.22);
59const SHADING_TINT: Color = Color::rgb(0.55, 0.55, 0.6);
60const RELIEF_TINT: Color = Color::rgb(0.5, 0.45, 0.35);
61const EMISSIVE_BASE: Color = Color::rgb(0.04, 0.04, 0.05);
62const EMISSIVE_GLOW: Color = Color::rgb(3.2, 2.2, 0.7);
63
64/// Texel side length of every generated map: coarse enough that each
65/// checker cell reads as a distinct part on a sphere or a cube face.
66const MAP_SIZE: UVec2 = UVec2::new(64, 64);
67
68/// Checker cell width, in texels, for the shading map.
69const SHADING_CELL: u32 = 8;
70/// The shading checker's two states: occlusion, roughness and metallic —
71/// one square low across all three, the other full across all three.
72const SHADING_LOW: [u8; 3] = [70, 40, 15];
73const SHADING_HIGH: [u8; 3] = [255, 225, 235];
74
75/// Checker cell width, in texels, for the emissive map.
76const EMISSIVE_CELL: u32 = 6;
77
78/// Wave count the relief's texture repeats across its map, and the peak
79/// slope of its surface, in height over distance.
80const BUMP_WAVES: f32 = 6.0;
81const BUMP_SLOPE: f32 = 1.15;
82
83/// `BannerCloth`'s width and height, in meters.
84const BANNER_WIDTH: f32 = 1.1;
85const BANNER_HEIGHT: f32 = 0.7;
86
87/// Columns `BannerCloth` splits into, so its wave curves smoothly.
88const BANNER_COLUMNS: u32 = 10;
89
90/// Where the pillars, the pole, the cloth and the pulsing sphere are
91/// placed, added to every one of their own positions: apart from the
92/// pairs and the reflection row, so a light's shadow has clear ground to
93/// land on.
94const OUTPOST: Vec3 = Vec3::new(-4.6, 0.0, -0.8);
95
96const PILLARS: [(Vec3, Vec3); 3] = [
97    (Vec3::new(-1.8, 0.6, -0.6), Vec3::new(0.6, 1.2, 0.6)),
98    (Vec3::new(0.4, 0.4, -1.6), Vec3::new(0.5, 0.8, 0.5)),
99    (Vec3::new(1.7, 0.9, 0.4), Vec3::new(0.55, 1.8, 0.55)),
100];
101
102const POLE_POSITION: Vec3 = Vec3::new(-2.6, 1.0, 0.4);
103const POLE_SCALE: Vec3 = Vec3::new(0.12, 2.0, 0.12);
104const BANNER_MOUNT: Vec3 = Vec3::new(-2.54, 1.55, 0.46);
105
106const FIELD_ORB_POSITION: Vec3 = Vec3::new(1.3, 1.1, 1.6);
107const FIELD_ORB_SCALE: f32 = 0.7;
108
109/// The lamp's fixed position and reach, in meters.
110const LAMP_POSITION: Vec3 = Vec3::new(2.4, 1.4, -3.0);
111const LAMP_RANGE: f32 = 5.0;
112
113/// The spotlight's fixed placement: where it is placed, which way its
114/// cone points, its reach in meters and its width in radians.
115const SPOT_POSITION: Vec3 = Vec3::new(-3.4, 3.0, 1.6);
116const SPOT_DIRECTION: Vec3 = Vec3::new(0.55, -1.0, -1.0);
117const SPOT_RANGE: f32 = 7.0;
118const SPOT_ANGLE: f32 = 0.5;
119
120/// The camera's vertical field of view, in degrees.
121const CAMERA_FOV: f32 = 46.0;
122/// Where the camera starts, and the `yaw` and the pitch, in radians, it
123/// starts turned to.
124const START_EYE: Vec3 = Vec3::new(-0.6, 2.2, 9.0);
125const START_YAW: f32 = 0.0;
126const START_PITCH: f32 = -0.15;
127/// How far short of straight up or down the pitch may turn, in radians,
128/// where a turn alone reads as nothing.
129const PITCH_LIMIT: f32 = 1.5;
130/// The `eye`'s least height above the ground plane, in meters: held above
131/// zero so a move can never take it below.
132const MIN_EYE_HEIGHT: f32 = 0.3;
133/// Radians the pointer's own motion turns the view by, per physical
134/// pixel it crosses, before the axis it reads through bounds it: a drag
135/// across the whole window turns about a quarter turn.
136const LOOK_SENSITIVITY: f32 = core::f32::consts::FRAC_PI_2 / 1280.0;
137/// Meters a move key covers per second, at [`Playground::speed_scale`]'s
138/// own default.
139const MOVE_SPEED: f32 = 4.0;
140/// The factor one full wheel step multiplies the move speed apart from.
141const SPEED_STEP: f32 = 1.5;
142/// The move speed's own least and most, as a factor of [`MOVE_SPEED`].
143const MIN_SPEED_SCALE: f32 = 0.2;
144const MAX_SPEED_SCALE: f32 = 5.0;
145
146/// Bloom this scene starts at, past the engine's own default: enough that
147/// [`EMISSIVE_GLOW`] and the brightest lights scatter right away.
148const START_BLOOM: f32 = 0.25;
149const START_EXPOSURE: f32 = 1.0;
150
151/// The sky a frame that keeps [`Sky::Default`] draws and is lit by: the
152/// same flat grey the engine falls back to when a frame sets none.
153const DEFAULT_SKY: Color = Color::rgb(0.1, 0.1, 0.1);
154
155/// The fraction of its own light each loaded sky lands and reflects,
156/// through [`SkyboxData::lit_by`]: the bright images fixed low, since an
157/// image read too bright under the frame's own lights at its default
158/// `1.0`; the dim images fixed more, since the scene read too dark under
159/// them at the bright images' value.
160const CLEAR_SKY_LIGHT: f32 = 0.35;
161const CLASSIC_SKY_LIGHT: f32 = 0.35;
162const DAWN_SKY_LIGHT: f32 = 0.3;
163const SINISTER_SKY_LIGHT: f32 = 0.6;
164const LIGHT_BLUE_STARS_LIGHT: f32 = 0.8;
165const BLUE_STARS_LIGHT: f32 = 0.8;
166
167/// The shading pair's plain half: a sphere given the shared shading
168/// material and no map.
169#[derive(Catalog, Clone, Copy, PartialEq, Eq, Hash)]
170struct ShadingPlain;
171
172impl Mesh for ShadingPlain {
173    fn build(&self, assets: &Assets) -> MeshData {
174        sphere_with_material(assets, shading_material())
175    }
176}
177
178/// The shading pair's mapped half: the same sphere and material, with its
179/// shading map (occlusion, roughness and metallic) baked in.
180#[derive(Catalog, Clone, Copy, PartialEq, Eq, Hash)]
181struct ShadingMapped;
182
183impl Mesh for ShadingMapped {
184    fn build(&self, assets: &Assets) -> MeshData {
185        sphere_with_material(assets, shading_material()).with_shading(shading_checker())
186    }
187}
188
189/// The relief pair's plain half.
190#[derive(Catalog, Clone, Copy, PartialEq, Eq, Hash)]
191struct ReliefPlain;
192
193impl Mesh for ReliefPlain {
194    fn build(&self, assets: &Assets) -> MeshData {
195        sphere_with_material(assets, relief_material())
196    }
197}
198
199/// The relief pair's mapped half: the same sphere and material, with its
200/// relief map baked in.
201#[derive(Catalog, Clone, Copy, PartialEq, Eq, Hash)]
202struct ReliefMapped;
203
204impl Mesh for ReliefMapped {
205    fn build(&self, assets: &Assets) -> MeshData {
206        sphere_with_material(assets, relief_material()).with_relief(relief_bumps())
207    }
208}
209
210/// The emissive pair's plain half.
211#[derive(Catalog, Clone, Copy, PartialEq, Eq, Hash)]
212struct EmissivePlain;
213
214impl Mesh for EmissivePlain {
215    fn build(&self, assets: &Assets) -> MeshData {
216        cube_with_material(assets, emissive_material())
217    }
218}
219
220/// The emissive pair's mapped half: the same cube and material, with its
221/// emissive map baked in.
222#[derive(Catalog, Clone, Copy, PartialEq, Eq, Hash)]
223struct EmissiveMapped;
224
225impl Mesh for EmissiveMapped {
226    fn build(&self, assets: &Assets) -> MeshData {
227        cube_with_material(assets, emissive_material()).with_emissive_map(emissive_checker())
228    }
229}
230
231/// The front sphere: a draw overrides its material new every frame, in
232/// place of a baked one.
233#[derive(Catalog, Clone, Copy, PartialEq, Eq, Hash)]
234struct Front;
235
236impl Mesh for Front {
237    fn build(&self, assets: &Assets) -> MeshData {
238        Sphere {
239            subdivisions: SPHERE_SUBDIVISIONS,
240        }
241        .build(assets)
242    }
243}
244
245/// `BannerCloth`: a mesh split into columns along its span and placed at
246/// its `x = 0` edge, so `Banner`'s wave curves it, not a single flat
247/// quad. Its triangles are built twice: once as authored and once in the
248/// other order, with the normal turned around, so the cloth draws from
249/// both sides however its wave curves it.
250#[derive(Catalog, Clone, Copy, PartialEq, Eq, Hash)]
251struct BannerCloth;
252
253impl Mesh for BannerCloth {
254    fn build(&self, _: &Assets) -> MeshData {
255        banner_mesh()
256    }
257}
258
259// Everything this game draws: the ground plane, each map pair's plain and
260// mapped half, the front sphere with its own live material, the built-in
261// primitives the reflection row and the pillars beside it place per draw,
262// and the displaced cloth.
263meshes! {
264    enum Shape {
265        Plane,
266        Sphere,
267        Cube,
268        ShadingPlain,
269        ShadingMapped,
270        ReliefPlain,
271        ReliefMapped,
272        EmissivePlain,
273        EmissiveMapped,
274        Front,
275        BannerCloth,
276    }
277}
278
279fn sphere_with_material(assets: &Assets, material: Material) -> MeshData {
280    Sphere {
281        subdivisions: SPHERE_SUBDIVISIONS,
282    }
283    .build(assets)
284    .with_material(material)
285}
286
287fn cube_with_material(assets: &Assets, material: Material) -> MeshData {
288    Cube.build(assets).with_material(material)
289}
290
291fn shading_material() -> Material {
292    Material::lit(SHADING_TINT).roughness(0.5).metallic(0.5)
293}
294
295fn relief_material() -> Material {
296    Material::lit(RELIEF_TINT).roughness(0.35)
297}
298
299fn emissive_material() -> Material {
300    Material::color(EMISSIVE_BASE).emissive(EMISSIVE_GLOW)
301}
302
303/// A shading map whose checker goes between low occlusion, roughness and
304/// metallic and full occlusion, roughness and metallic, so all three read
305/// apart across [`ShadingMapped`].
306fn shading_checker() -> ShadingData {
307    ShadingData::rgba8(
308        MAP_SIZE,
309        checker_pixels(MAP_SIZE, SHADING_CELL, SHADING_LOW, SHADING_HIGH),
310    )
311}
312
313/// An emissive map whose checker goes between full glow and none, so
314/// [`EMISSIVE_GLOW`] shapes across [`EmissiveMapped`] instead of casting
315/// whole.
316fn emissive_checker() -> TextureData {
317    TextureData::rgba8(
318        MAP_SIZE,
319        checker_pixels(MAP_SIZE, EMISSIVE_CELL, [0, 0, 0], [255, 255, 255]),
320    )
321}
322
323fn checker_pixels(size: UVec2, cell: u32, low: [u8; 3], high: [u8; 3]) -> Vec<u8> {
324    let mut pixels = Vec::with_capacity((size.x * size.y * 4) as usize);
325    for y in 0..size.y {
326        for x in 0..size.x {
327            let on = ((x / cell) + (y / cell)).is_multiple_of(2);
328            let [red, green, blue] = if on { high } else { low };
329            pixels.extend_from_slice(&[red, green, blue, u8::MAX]);
330        }
331    }
332    pixels
333}
334
335/// A relief whose normals turn across a wave that repeats over the map:
336/// each texel's slope comes from the partial derivatives of a
337/// `sin(u) * sin(v)` height field at `BUMP_SLOPE`'s peak, computed at that
338/// texel and not sampled from any other.
339fn relief_bumps() -> ReliefData {
340    let size = MAP_SIZE;
341    let turns = core::f32::consts::TAU * BUMP_WAVES;
342    let mut pixels = Vec::with_capacity((size.x * size.y * 4) as usize);
343    for y in 0..size.y {
344        for x in 0..size.x {
345            let u = (x as f32 + 0.5) / size.x as f32;
346            let v = (y as f32 + 0.5) / size.y as f32;
347            let slope_u = BUMP_SLOPE * (turns * u).cos() * (turns * v).sin();
348            let slope_v = BUMP_SLOPE * (turns * u).sin() * (turns * v).cos();
349            let normal = Vec3::new(-slope_u, -slope_v, 1.0).normalize();
350            let encode = |signed: f32| ((signed * 0.5 + 0.5) * 255.0).round() as u8;
351            pixels.extend_from_slice(&[encode(normal.x), encode(normal.y), encode(normal.z), 0]);
352        }
353    }
354    ReliefData::normals(size, pixels)
355}
356
357/// `BannerCloth`'s vertices and indices, built twice over: the columns as
358/// authored, facing `+Z`, and the same columns again facing `-Z`, their
359/// triangles in the other order so both draw front side out.
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}
405
406/// Displaced by a wave that grows away from its `x = 0` edge; casts the
407/// shadow of where it was placed, unmoved by its own wave. Its one value
408/// is the clock its wave slides on.
409#[derive(Default, ShaderValues)]
410struct Banner {
411    time: f32,
412}
413
414impl SurfaceStyle for Banner {
415    const PASS: DrawPass = DrawPass::Opaque;
416    const DISPLACE: Option<&'static str> = Some(include_str!("material_playground_banner.wgsl"));
417}
418
419/// A surface that reads no light of the scene's own: it draws its own
420/// pulsing tint, added over what is behind it, through the color it pulses
421/// through and the clock the pulse is timed by.
422#[derive(Default, ShaderValues)]
423struct Field {
424    tint: Color,
425    time: f32,
426}
427
428impl SurfaceStyle for Field {
429    const PASS: DrawPass = DrawPass::Additive;
430    const SURFACE: Option<&'static str> = Some(include_str!("material_playground_field.wgsl"));
431}
432
433surface_styles! { enum Looks { Banner, Field } }
434
435/// A whole scene lighting choice: it names a sky and, kept with it, the
436/// sun that lights the scene, so a choice cannot leave the two apart.
437/// `Dawn`, `Noon`, `Dusk` and `Night` each pair a gradient with a sun of
438/// its own color and direction; `Clear`, `Classic`, `ImageDawn` and
439/// `Sinister` each pair a loaded image with a sun that fits it, and
440/// `LightBlueStars` and `BlueStars` pair a loaded space image with none;
441/// `Default` is the engine's own grey sky and white sun.
442///
443/// [`Skyboxes`] proves every value at startup, so it must be [`Eq`] and
444/// [`Hash`] over a fixed [`Skyboxes::catalog`] — a sky and sun a player
445/// set to any color and direction live could never meet, since `f32` is
446/// neither. This fixed, named set is the shape this file chose in its
447/// place: the side area offers it as one row, and shows the chosen sky's
448/// own light and its sun's own strength as text, read only, rather than
449/// controls a game could not build from. See this example's report for
450/// what that choice costs.
451#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
452enum Sky {
453    Dawn,
454    Noon,
455    Dusk,
456    Night,
457    Clear,
458    Classic,
459    ImageDawn,
460    Sinister,
461    LightBlueStars,
462    BlueStars,
463    Default,
464}
465
466impl Sky {
467    const ALL: [Sky; 11] = [
468        Self::Dawn,
469        Self::Noon,
470        Self::Dusk,
471        Self::Night,
472        Self::Clear,
473        Self::Classic,
474        Self::ImageDawn,
475        Self::Sinister,
476        Self::LightBlueStars,
477        Self::BlueStars,
478        Self::Default,
479    ];
480
481    fn name(self) -> &'static str {
482        match self {
483            Self::Dawn => "dawn",
484            Self::Noon => "noon",
485            Self::Dusk => "dusk",
486            Self::Night => "night",
487            Self::Clear => "clear day",
488            Self::Classic => "classic",
489            Self::ImageDawn => "dawn image",
490            Self::Sinister => "sinister night",
491            Self::LightBlueStars => "light blue stars",
492            Self::BlueStars => "blue stars",
493            Self::Default => "default",
494        }
495    }
496
497    /// The fraction of its own light this sky lands and reflects, through
498    /// [`SkyboxData::lit_by`]: fixed per choice, so a bright one does not
499    /// read too bright, and a dark one does not read too dark, under the
500    /// frame's own lights.
501    fn light(self) -> f32 {
502        match self {
503            Self::Dawn => 0.4,
504            Self::Noon => 0.5,
505            Self::Dusk => 0.35,
506            Self::Night => 0.3,
507            Self::Clear => CLEAR_SKY_LIGHT,
508            Self::Classic => CLASSIC_SKY_LIGHT,
509            Self::ImageDawn => DAWN_SKY_LIGHT,
510            Self::Sinister => SINISTER_SKY_LIGHT,
511            Self::LightBlueStars => LIGHT_BLUE_STARS_LIGHT,
512            Self::BlueStars => BLUE_STARS_LIGHT,
513            Self::Default => 1.0,
514        }
515    }
516
517    /// The sun this choice pairs with its sky: direction, color and
518    /// strength resolved together, so a choice cannot leave them apart.
519    /// `None` for the two space images, which pair with no sun at all.
520    fn sun(self) -> Option<(Vec3, Color, f32)> {
521        match self {
522            Self::Dawn => Some((
523                Vec3::new(-1.0, -0.15, 0.05),
524                Color::rgb(1.0, 0.7, 0.45),
525                1.4,
526            )),
527            Self::Noon => Some((
528                Vec3::new(-0.15, -1.0, -0.1),
529                Color::rgb(1.0, 1.0, 0.98),
530                1.6,
531            )),
532            Self::Dusk => Some((
533                Vec3::new(1.0, -0.15, 0.05),
534                Color::rgb(1.0, 0.55, 0.25),
535                1.2,
536            )),
537            Self::Night => Some((
538                Vec3::new(-0.3, -0.7, -0.6),
539                Color::rgb(0.55, 0.65, 0.85),
540                0.15,
541            )),
542            Self::Clear => Some((
543                Vec3::new(-0.2, -1.0, -0.15),
544                Color::rgb(1.0, 0.98, 0.9),
545                1.5,
546            )),
547            Self::Classic => Some((
548                Vec3::new(-0.4, -0.9, -0.2),
549                Color::rgb(1.0, 0.95, 0.85),
550                1.3,
551            )),
552            Self::ImageDawn => Some((Vec3::new(-1.0, -0.2, 0.1), Color::rgb(1.0, 0.75, 0.5), 1.1)),
553            Self::Sinister => Some((Vec3::new(0.4, -0.5, -0.7), Color::rgb(0.4, 0.5, 0.75), 0.1)),
554            Self::LightBlueStars | Self::BlueStars => None,
555            Self::Default => Some((Vec3::new(-0.4, -1.0, -0.6), Color::WHITE, 1.0)),
556        }
557    }
558
559    /// The color the sky reads under the horizon, through
560    /// [`SkyboxData::with_ground`]: the floor as lit under this choice's own
561    /// sun and [`Self::light`], so it moves with them, not only with the
562    /// image. `None` for the gradient skies and `Default`, which need no
563    /// ground, and for the two space images, which hold space below the
564    /// horizon as well.
565    fn ground(self) -> Option<Color> {
566        match self {
567            Self::Clear => Some(Color::rgb(0.501, 0.517, 0.449)),
568            Self::Classic => Some(Color::rgb(0.420, 0.405, 0.379)),
569            Self::ImageDawn => Some(Color::rgb(0.073, 0.053, 0.032)),
570            Self::Sinister => Some(Color::rgb(0.012, 0.014, 0.020)),
571            Self::Dawn
572            | Self::Noon
573            | Self::Dusk
574            | Self::Night
575            | Self::LightBlueStars
576            | Self::BlueStars
577            | Self::Default => None,
578        }
579    }
580}
581
582impl Catalog for Sky {
583    fn catalog() -> Vec<Self> {
584        Self::ALL.to_vec()
585    }
586}
587
588impl Skyboxes for Sky {
589    fn build(&self, assets: &Assets) -> SkyboxData {
590        let sky = match self {
591            Self::Dawn => SkyboxData::gradient(
592                Color::rgb(0.55, 0.55, 0.75),
593                Color::rgb(0.95, 0.6, 0.35),
594                Color::rgb(0.12, 0.08, 0.06),
595            ),
596            Self::Noon => SkyboxData::gradient(
597                Color::rgb(0.2, 0.45, 0.85),
598                Color::rgb(0.75, 0.82, 0.9),
599                Color::rgb(0.3, 0.3, 0.28),
600            ),
601            Self::Dusk => SkyboxData::gradient(
602                Color::rgb(0.18, 0.1, 0.3),
603                Color::rgb(0.85, 0.35, 0.2),
604                Color::rgb(0.03, 0.02, 0.03),
605            ),
606            Self::Night => SkyboxData::gradient(
607                Color::rgb(0.02, 0.02, 0.06),
608                Color::rgb(0.05, 0.05, 0.1),
609                Color::rgb(0.0, 0.0, 0.0),
610            ),
611            Self::Clear => assets.skybox("sky-clear"),
612            Self::Classic => assets.skybox("sky-classic"),
613            Self::ImageDawn => assets.skybox("sky-dawn"),
614            Self::Sinister => assets.skybox("sky-sinister"),
615            Self::LightBlueStars => assets.skybox("sky-stars-lightblue"),
616            Self::BlueStars => assets.skybox("sky-stars-blue"),
617            Self::Default => SkyboxData::gradient(DEFAULT_SKY, DEFAULT_SKY, DEFAULT_SKY),
618        };
619        let sky = match self.ground() {
620            Some(ground) => sky.with_ground(ground),
621            None => sky,
622        };
623
624        sky.lit_by(self.light())
625    }
626}
627
628/// `color` scaled by `strength`, the value a [`Light`] reads.
629fn scaled(color: Color, strength: f32) -> Color {
630    Color::rgb(
631        color.red * strength,
632        color.green * strength,
633        color.blue * strength,
634    )
635}
636
637/// One light's color and strength, held apart from the position that
638/// names it, plus whether it casts.
639#[derive(Clone, Copy)]
640struct Glow {
641    color: Color,
642    strength: f32,
643    shadow: bool,
644}
645
646impl Glow {
647    /// `color` scaled by `strength`, the value a [`Light`] reads.
648    fn scaled(self) -> Color {
649        scaled(self.color, self.strength)
650    }
651}
652
653/// Every key and button this game reads apart from the UI: held, `Look`
654/// turns the camera by the pointer's own motion, `Forward`/`Back`/
655/// `Left`/`Right` move it along the view and to its side, and `Up`/
656/// `Down` move it along the world's own up.
657#[derive(InputButtonAction, Clone, Copy, PartialEq)]
658enum Move {
659    Forward,
660    Back,
661    Left,
662    Right,
663    Up,
664    Down,
665    Look,
666}
667
668impl InputButtonAction for Move {
669    fn bindings(&self) -> Vec<ButtonBinding> {
670        match self {
671            Self::Forward => vec![Key::W.into()],
672            Self::Back => vec![Key::S.into()],
673            Self::Left => vec![Key::A.into()],
674            Self::Right => vec![Key::D.into()],
675            Self::Up => vec![Key::Space.into()],
676            Self::Down => vec![Key::LeftShift.into()],
677            Self::Look => vec![MouseButton::Right.into()],
678        }
679    }
680}
681
682/// The pointer's own motion, read only while [`Move::Look`] is held.
683#[derive(InputAxis2Action, Clone, Copy, PartialEq)]
684enum Turn {
685    Look,
686}
687
688impl InputAxis2Action for Turn {
689    fn bindings(&self) -> Vec<Axis2Binding> {
690        match self {
691            Self::Look => vec![Axis2Binding::pointer().scale(LOOK_SENSITIVITY)],
692        }
693    }
694}
695
696/// How far the wheel moved this frame, read to scale the move speed.
697#[derive(InputAxisAction, Clone, Copy, PartialEq)]
698enum Speed {
699    Wheel,
700}
701
702impl InputAxisAction for Speed {
703    fn bindings(&self) -> Vec<AxisBinding> {
704        match self {
705            Self::Wheel => vec![AxisBinding::from(WheelDelta::Up).scale(4.0)],
706        }
707    }
708}
709
710struct Controls;
711
712impl InputActions for Controls {
713    type Button = Move;
714    type Axis = Speed;
715    type Axis2 = Turn;
716}
717
718struct Playground {
719    eye: Vec3,
720    yaw: f32,
721    pitch: f32,
722    speed_scale: f32,
723
724    sky: Sky,
725    sun_shadow: bool,
726
727    lamp: Glow,
728    spotlight: Glow,
729
730    front_tint: Color,
731    front_roughness: f32,
732    front_metallic: f32,
733    shading_map_on: bool,
734    relief_map_on: bool,
735    emissive_map_on: bool,
736
737    exposure: f32,
738    bloom: f32,
739}
740
741impl Playground {
742    fn init(ctx: &mut InitContext<'_, Self>) -> Result<Self, Error> {
743        let _ = ctx;
744        Ok(Self {
745            eye: START_EYE,
746            yaw: START_YAW,
747            pitch: START_PITCH,
748            speed_scale: 1.0,
749
750            sky: Sky::Default,
751            sun_shadow: true,
752
753            lamp: Glow {
754                color: Color::rgb(0.9, 0.55, 0.3),
755                strength: 3.0,
756                shadow: false,
757            },
758            spotlight: Glow {
759                color: Color::rgb(0.4, 0.6, 1.0),
760                strength: 6.0,
761                shadow: true,
762            },
763
764            front_tint: Color::rgb(0.7, 0.25, 0.2),
765            front_roughness: 0.4,
766            front_metallic: 0.0,
767            shading_map_on: true,
768            relief_map_on: true,
769            emissive_map_on: true,
770
771            exposure: START_EXPOSURE,
772            bloom: START_BLOOM,
773        })
774    }
775
776    /// This frame's forward direction, from `yaw` (turning around the
777    /// world's own up) and `pitch` (turning up or down).
778    fn forward(&self) -> Vec3 {
779        Vec3::new(
780            -self.pitch.cos() * self.yaw.sin(),
781            self.pitch.sin(),
782            -self.pitch.cos() * self.yaw.cos(),
783        )
784    }
785
786    /// The camera this frame draws from: `eye` looking along `forward`.
787    fn camera(&self) -> Camera {
788        Camera::new(
789            View::look_at(self.eye, self.eye + self.forward()),
790            Projection::perspective(CAMERA_FOV),
791        )
792    }
793
794    /// A held `Move::Look` (the right mouse button) turns the camera by
795    /// the pointer's own motion, the same way it moves: dragging right
796    /// turns the view right and left turns it left, dragging down turns
797    /// it to look further down at the scene, dragging up back toward the
798    /// horizon. `W`/`A`/`S`/`D` move along the view and to its side,
799    /// `Space`/`Left Shift` up and down, and the wheel scales how far
800    /// each move goes. The `eye` is held above the ground plane wherever
801    /// it moves.
802    fn fly_camera(&mut self, ctx: &mut FrameContext<'_, Self>) {
803        if !ctx.ui_wants_pointer() && ctx.down(Move::Look) {
804            let look = ctx.axis2(Turn::Look);
805            self.yaw -= look.x;
806            self.pitch = (self.pitch + look.y).clamp(-PITCH_LIMIT, PITCH_LIMIT);
807        }
808
809        let wheel = ctx.axis(Speed::Wheel);
810        if !ctx.ui_wants_pointer() && wheel != 0.0 {
811            self.speed_scale =
812                (self.speed_scale * SPEED_STEP.powf(wheel)).clamp(MIN_SPEED_SCALE, MAX_SPEED_SCALE);
813        }
814
815        let forward = self.forward();
816        let right = Vec3::new(self.yaw.cos(), 0.0, -self.yaw.sin());
817        let mut move_by = Vec3::ZERO;
818        if ctx.down(Move::Forward) {
819            move_by += forward;
820        }
821        if ctx.down(Move::Back) {
822            move_by -= forward;
823        }
824        if ctx.down(Move::Right) {
825            move_by += right;
826        }
827        if ctx.down(Move::Left) {
828            move_by -= right;
829        }
830        if ctx.down(Move::Up) {
831            move_by += Vec3::Y;
832        }
833        if ctx.down(Move::Down) {
834            move_by -= Vec3::Y;
835        }
836        if move_by.length_squared() > 1.0 {
837            move_by = move_by.normalize();
838        }
839
840        self.eye += move_by * MOVE_SPEED * self.speed_scale * ctx.dt().as_secs_f32();
841        self.eye.y = self.eye.y.max(MIN_EYE_HEIGHT);
842    }
843
844    /// The material [`Front`] draws with, resolved new from its sliders
845    /// every frame — the override [`Instance::material`] takes, in place
846    /// of a baked one.
847    fn front_material(&self) -> Material {
848        Material::lit(self.front_tint)
849            .roughness(self.front_roughness)
850            .metallic(self.front_metallic)
851    }
852
853    /// Every draw this game makes: the ground, each map pair, the front
854    /// sphere, the reflection row and the pillars beside it.
855    fn draw_scene(&self, ctx: &mut FrameContext<'_, Self>) {
856        ctx.draw(
857            Plane
858                .at(Transform::from_scale(Vec3::new(
859                    GROUND_SIZE,
860                    1.0,
861                    GROUND_SIZE,
862                )))
863                .material(Material::lit(GROUND_COLOR).roughness(0.9)),
864        );
865
866        Self::draw_pair(
867            ctx,
868            SHADING_Z,
869            SPHERE_RADIUS,
870            ShadingPlain.at(Vec3::ZERO).into_set(),
871            ShadingMapped.at(Vec3::ZERO).into_set(),
872            self.shading_map_on,
873        );
874        Self::draw_pair(
875            ctx,
876            RELIEF_Z,
877            SPHERE_RADIUS,
878            ReliefPlain.at(Vec3::ZERO).into_set(),
879            ReliefMapped.at(Vec3::ZERO).into_set(),
880            self.relief_map_on,
881        );
882        Self::draw_pair(
883            ctx,
884            EMISSIVE_Z,
885            CUBE_SIZE / 2.0,
886            EmissivePlain.at(Vec3::ZERO).into_set(),
887            EmissiveMapped.at(Vec3::ZERO).into_set(),
888            self.emissive_map_on,
889        );
890
891        ctx.draw(
892            Front
893                .at(Transform::from_scale_rotation_translation(
894                    Vec3::splat(FRONT_SCALE),
895                    Quat::IDENTITY,
896                    FRONT_POSITION,
897                ))
898                .material(self.front_material()),
899        );
900
901        self.draw_reflect_row(ctx);
902        self.draw_outpost(ctx);
903    }
904
905    /// One pair at depth `z`, its centers `height` above the ground: `plain`
906    /// on the left always, and on the right `mapped` where `mapped_on` is
907    /// set, `plain` again where it is not — the same position drawing the
908    /// same base material with and without the map.
909    fn draw_pair(
910        ctx: &mut FrameContext<'_, Self>,
911        z: f32,
912        height: f32,
913        plain: Instance<Shape, Looks>,
914        mapped: Instance<Shape, Looks>,
915        mapped_on: bool,
916    ) {
917        ctx.draw(plain.clone().at(Vec3::new(-PAIR_HALF_SPACING, height, z)));
918        let right = if mapped_on { mapped } else { plain };
919        ctx.draw(right.at(Vec3::new(PAIR_HALF_SPACING, height, z)));
920    }
921
922    /// A row of built-in `Sphere` draws at rising roughness, each
923    /// `metallic(1.0)` with its tint white, so what draws is the sky's own
924    /// reflection alone.
925    fn draw_reflect_row(&self, ctx: &mut FrameContext<'_, Self>) {
926        let start = -REFLECT_ROW_SPACING * (REFLECT_ROW_COUNT as f32 - 1.0) / 2.0;
927        for index in 0..REFLECT_ROW_COUNT {
928            let x = start + index as f32 * REFLECT_ROW_SPACING;
929            let roughness = index as f32 / (REFLECT_ROW_COUNT as f32 - 1.0);
930            ctx.draw(
931                Sphere {
932                    subdivisions: SPHERE_SUBDIVISIONS,
933                }
934                .at(Transform::from_scale_rotation_translation(
935                    Vec3::splat(REFLECT_ROW_RADIUS * 2.0),
936                    Quat::IDENTITY,
937                    Vec3::new(x, REFLECT_ROW_RADIUS, REFLECT_ROW_Z),
938                ))
939                .material(
940                    Material::lit(Color::WHITE)
941                        .roughness(roughness)
942                        .metallic(1.0),
943                ),
944            );
945        }
946    }
examples/flock-parallelism.rs (line 102)
102const SUN_DIRECTION: Vec3 = Vec3::new(-0.8, -0.55, -0.5);
103const SUN_COLOR: Color = Color::rgb(0.95, 0.92, 0.85);
104
105const SKY_ZENITH: Color = Color::rgb(0.15, 0.22, 0.42);
106const SKY_HORIZON: Color = Color::rgb(0.7, 0.48, 0.34);
107const SKY_NADIR: Color = Color::rgb(0.2, 0.16, 0.14);
108const SKY_LIGHT: f32 = 0.65;
109/// The sky's own color under the horizon, so a butterfly's own underside and
110/// the ground reflect light from the sky instead of reading black.
111const SKY_GROUND: Color = Color::rgb(0.42, 0.34, 0.28);
112
113/// The camera's height above and distance from the flock's center, each a
114/// fraction of the flock's own radius, so the view frames every size.
115const CAMERA_HEIGHT_FRACTION: f32 = 0.45;
116const CAMERA_DISTANCE_FRACTION: f32 = 2.2;
117/// How far above the flock's center the camera looks, as a fraction of
118/// the flock's own radius, so the flock sits below the panel.
119const CAMERA_AIM_LIFT_FRACTION: f32 = 0.25;
120const CAMERA_FOV: f32 = 75.0;
121/// Radians the camera turns about the flock's center per second.
122const CAMERA_ANGULAR_SPEED: f32 = 0.08;
123
124/// Butterflies a chunk of [`Butterflies::center`]'s fold sums at a time, so the sum
125/// reads the same bits at any worker count.
126const CENTER_CHUNK_SIZE: usize = 1024;
127
128const PANEL_PADDING: i8 = 8;
129
130meshes! { enum Shape { Butterfly, Plane } }
131
132/// The one sky this game draws, a gradient set each frame.
133#[derive(Catalog, Clone, Copy, Debug, Eq, Hash, PartialEq)]
134enum Sky {
135    Day,
136}
137
138impl Skyboxes for Sky {
139    fn build(&self, _assets: &Assets) -> SkyboxData {
140        match self {
141            Self::Day => SkyboxData::gradient(SKY_ZENITH, SKY_HORIZON, SKY_NADIR)
142                .lit_by(SKY_LIGHT)
143                .with_ground(SKY_GROUND),
144        }
145    }
146}
147
148#[derive(Catalog, Clone, PartialEq, Eq, Hash)]
149struct Butterfly;
150
151/// The one clip a butterfly is posed by, named as the source names it.
152#[derive(Clip, Clone, Debug, PartialEq, Eq, Hash)]
153enum ButterflyClip {
154    #[clip("fly")]
155    Fly,
156}
157
158impl Mesh<NoParts, ButterflyClip> for Butterfly {
159    fn build(&self, assets: &Assets) -> MeshData<NoParts, ButterflyClip> {
160        assets
161            .model(BUTTERFLY_ROOT)
162            .with_texture(greyed(&assets.texture(BUTTERFLY_SKIN)))
163    }
164}
165
166/// `skin` with every texel at its own grey level, its alpha kept, so a tint
167/// colors it whole.
168fn greyed(skin: &TextureData) -> TextureData {
169    let pixels = skin
170        .pixels()
171        .chunks_exact(4)
172        .flat_map(|texel| {
173            let [red, green, blue, alpha] = [texel[0], texel[1], texel[2], texel[3]];
174            let grey =
175                (0.2126 * f32::from(red) + 0.7152 * f32::from(green) + 0.0722 * f32::from(blue))
176                    .round() as u8;
177            [grey, grey, grey, alpha]
178        })
179        .collect();
180    TextureData::rgba8(skin.size(), pixels)
181}
182
183/// What one butterfly looks like: the flap group that poses it and the tint
184/// it is drawn in, both its own for the whole run.
185#[derive(Clone, Copy, Default)]
186struct Kind {
187    flap: u8,
188    tint: u8,
189}
190
191impl Kind {
192    /// The kind butterfly `index` is given, by its own integer-hash.
193    fn of(index: u32) -> Self {
194        Self {
195            flap: (hash(index, 4) % FLAP_GROUPS as u32) as u8,
196            tint: (hash(index, 5) % TINTS.len() as u32) as u8,
197        }
198    }
199}
200
201/// A machine of one state, holding the flap at the phase it is given: one
202/// machine per flap group poses every butterfly of that group.
203#[derive(Clone, Copy, Eq, PartialEq, Debug)]
204enum FlapState {
205    Flapping,
206}
207
208impl AnimationStates for FlapState {
209    type Clip = ButterflyClip;
210    type Input = f32;
211
212    fn entry() -> Self {
213        Self::Flapping
214    }
215
216    fn motion(&self, phase: &f32) -> Motion<ButterflyClip> {
217        Motion::scrubbed(ButterflyClip::Fly, *phase)
218    }
219
220    fn next(&self, _phase: &f32, _at: Progress) -> Option<Transition<Self>> {
221        None
222    }
223}
224
225/// The phase flap group `group` holds `flown` seconds into the run: the
226/// flaps its own rate has run, spread about [`FLAP_RATE`] by
227/// [`FLAP_RATE_SPREAD`], the rate rising and falling by [`FLAP_WAVES`] at
228/// the group's own offsets, from the group's own start. The count is the
229/// sum of the rate over the seconds `flown`, taken whole rather than tick by
230/// tick, so no step adds up an error.
231fn flap_phase(group: usize, flown: f32) -> f32 {
232    let share = group as f32 / FLAP_GROUPS as f32;
233    let rate = FLAP_RATE * (1.0 + FLAP_RATE_SPREAD * (share - 0.5));
234    let waved: f32 = FLAP_WAVES
235        .iter()
236        .enumerate()
237        .map(|(wave, &(depth, period))| {
238            let angular = TAU / period;
239            let offset = hash_unit(group as u32, 6 + wave as u32) * TAU;
240            -depth / angular * (angular * flown + offset).cos()
241        })
242        .sum();
243    (rate * (flown + waved) + share).fract()
244}
245
246/// The sphere the flock is bound to: its center, held above the ground by
247/// [`FLOCK_CLEARANCE`], and its radius, sized so each butterfly has
248/// [`WORLD_VOLUME_PER_BUTTERFLY`].
249#[derive(Clone, Copy)]
250struct World {
251    center: Vec3A,
252    radius: f32,
253}
254
255impl World {
256    fn for_flock(count: u32) -> Self {
257        let radius = (count as f32 * WORLD_VOLUME_PER_BUTTERFLY * 3.0 / (4.0 * PI)).cbrt();
258        Self {
259            center: Vec3A::new(0.0, radius + FLOCK_CLEARANCE, 0.0),
260            radius,
261        }
262    }
263}
264
265/// The box of cells the flock is kept in: cubes [`NEIGHBOR_RADIUS`] across,
266/// `side` to an axis, from `least` on every axis.
267#[derive(Clone, Copy)]
268struct Cells {
269    side: usize,
270    least: Vec3A,
271}
272
273impl Cells {
274    /// The box over `world`'s sphere and [`BOX_MARGIN`] past it.
275    fn covering(world: World) -> Self {
276        let reach = world.radius * BOX_MARGIN;
277        let side = ((2.0 * reach) / NEIGHBOR_RADIUS).ceil().max(1.0) as usize;
278        Self {
279            side,
280            least: world.center - Vec3A::splat(reach),
281        }
282    }
283
284    /// How many cells the box holds.
285    fn count(self) -> usize {
286        self.side * self.side * self.side
287    }
288
289    /// The cell `position` lies in, held inside the box on every axis.
290    fn of(self, position: Vec3A) -> usize {
291        let scaled = (position - self.least) / NEIGHBOR_RADIUS;
292        let most = (self.side - 1) as f32;
293        let x = scaled.x.clamp(0.0, most) as usize;
294        let y = scaled.y.clamp(0.0, most) as usize;
295        let z = scaled.z.clamp(0.0, most) as usize;
296        (x * self.side + y) * self.side + z
297    }
298
299    /// `cell` and every cell beside it, at most 27, inside the box.
300    fn around(self, cell: usize) -> impl Iterator<Item = usize> {
301        let side = self.side;
302        let z = cell % side;
303        let y = (cell / side) % side;
304        let x = cell / (side * side);
305        let span = move |at: usize| at.saturating_sub(1)..(at + 2).min(side);
306        span(x).flat_map(move |cx| {
307            span(y).flat_map(move |cy| span(z).map(move |cz| (cx * side + cy) * side + cz))
308        })
309    }
310}
311
312/// The flock: every butterfly's position and velocity in cell order, where each
313/// cell's butterflies start, and the arrays the next tick is written into.
314struct Butterflies {
315    cells: Cells,
316    /// Where each cell's butterflies start, and one more for the end of the last.
317    start: Vec<u32>,
318    position: Vec<Vec3A>,
319    velocity: Vec<Vec3A>,
320    /// Each butterfly's kind, in the same order.
321    kind: Vec<Kind>,
322    next_position: Vec<Vec3A>,
323    next_velocity: Vec<Vec3A>,
324    next_kind: Vec<Kind>,
325    /// The cell each butterfly of the next arrays lies in.
326    next_cell: Vec<u32>,
327}
328
329impl Butterflies {
330    /// `count` butterflies scattered through `world`'s sphere, each with a level
331    /// heading at [`MIN_SPEED`], sorted into cells.
332    fn scattered(count: u32, world: World) -> Self {
333        let cells = Cells::covering(world);
334        let count = count as usize;
335        let mut swarm = Self {
336            cells,
337            start: vec![0; cells.count() + 1],
338            position: vec![Vec3A::ZERO; count],
339            velocity: vec![Vec3A::ZERO; count],
340            kind: vec![Kind::default(); count],
341            next_position: Vec::with_capacity(count),
342            next_velocity: Vec::with_capacity(count),
343            next_kind: Vec::with_capacity(count),
344            next_cell: Vec::with_capacity(count),
345        };
346        for index in 0..count as u32 {
347            let radius = world.radius * hash_unit(index, 0).cbrt();
348            let inclination = hash_unit(index, 1) * PI;
349            let azimuth = hash_unit(index, 2) * TAU;
350            let position = world.center
351                + Vec3A::new(
352                    radius * inclination.sin() * azimuth.cos(),
353                    radius * inclination.cos(),
354                    radius * inclination.sin() * azimuth.sin(),
355                );
356            let heading = hash_unit(index, 3) * TAU;
357            let velocity = Vec3A::new(heading.cos(), 0.0, heading.sin()) * MIN_SPEED;
358            swarm.next_position.push(position);
359            swarm.next_velocity.push(velocity);
360            swarm.next_kind.push(Kind::of(index));
361            swarm.next_cell.push(cells.of(position) as u32);
362        }
363        swarm.sort();
364        swarm
365    }
366
367    /// One tick of `dt` seconds: every cell's butterflies steered against the
368    /// cells around it into the next arrays, on the engine's workers or one
369    /// cell after another, then the next arrays sorted into cells again.
370    fn step(&mut self, dt: f32, world: World, sequential: bool) {
371        let Self {
372            cells,
373            start,
374            position,
375            velocity,
376            kind,
377            next_position,
378            next_velocity,
379            next_kind,
380            next_cell,
381        } = self;
382        let flock = Flying {
383            cells: *cells,
384            start,
385            position,
386            velocity,
387            kind,
388        };
389        let mut out = CellOut::split(&flock, next_position, next_velocity, next_kind, next_cell);
390        let steer = |(cell, out): (usize, &mut CellOut<'_>)| flock.steer(cell, out, dt, world);
391        if sequential {
392            out.iter_mut().enumerate().for_each(steer);
393        } else {
394            out.par_iter_mut().enumerate().for_each(steer);
395        }
396        drop(out);
397        self.sort();
398    }
399
400    /// Sorts the next arrays into the current ones by cell: a count per
401    /// cell, a running total, and a placement in order, so the result reads
402    /// the same at any worker count.
403    fn sort(&mut self) {
404        self.start.iter_mut().for_each(|start| *start = 0);
405        for &cell in &self.next_cell {
406            self.start[cell as usize + 1] += 1;
407        }
408        for cell in 0..self.cells.count() {
409            self.start[cell + 1] += self.start[cell];
410        }
411        let mut fill = self.start.clone();
412        for (index, &cell) in self.next_cell.iter().enumerate() {
413            let at = fill[cell as usize] as usize;
414            fill[cell as usize] += 1;
415            self.position[at] = self.next_position[index];
416            self.velocity[at] = self.next_velocity[index];
417            self.kind[at] = self.next_kind[index];
418        }
419    }
420
421    /// The flock's center: positions summed in fixed chunks and then
422    /// folded in order, the fold that `mirage_engine::rayon`'s own docs
423    /// show, so it reads the same bits at any worker count.
424    fn center(&self) -> Vec3A {
425        if self.position.is_empty() {
426            return Vec3A::ZERO;
427        }
428        let sum: Vec3A = self
429            .position
430            .par_chunks(CENTER_CHUNK_SIZE)
431            .map(|chunk| chunk.iter().copied().sum::<Vec3A>())
432            .collect::<Vec<Vec3A>>()
433            .into_iter()
434            .sum();
435        sum / self.position.len() as f32
436    }
437
438    /// Every butterfly's position, velocity and kind, in cell order.
439    fn each(&self) -> impl Iterator<Item = (Vec3A, Vec3A, Kind)> + '_ {
440        self.position
441            .iter()
442            .zip(&self.velocity)
443            .zip(&self.kind)
444            .map(|((&position, &velocity), &kind)| (position, velocity, kind))
445    }
446}
447
448/// The flock as one tick reads it: the current arrays and the cells they
449/// are sorted by, shared by every cell's step.
450struct Flying<'a> {
451    cells: Cells,
452    start: &'a [u32],
453    position: &'a [Vec3A],
454    velocity: &'a [Vec3A],
455    kind: &'a [Kind],
456}
457
458impl Flying<'_> {
459    /// The butterflies of `cell`, as a range of the arrays.
460    fn range(&self, cell: usize) -> Range<usize> {
461        self.start[cell] as usize..self.start[cell + 1] as usize
462    }
463
464    /// Steers every butterfly of `cell` by separation, alignment and cohesion
465    /// against the butterflies of the cells around it, and back toward the center
466    /// once it leaves `world`'s sphere, writing the next state into `out`.
467    fn steer(&self, cell: usize, out: &mut CellOut<'_>, dt: f32, world: World) {
468        let mut around: [Range<usize>; 27] = core::array::from_fn(|_| 0..0);
469        let mut near_count = 0;
470        for near in self.cells.around(cell) {
471            around[near_count] = self.range(near);
472            near_count += 1;
473        }
474        let around = &around[..near_count];
475
476        for (at, index) in self.range(cell).enumerate() {
477            let position = self.position[index];
478            let velocity = self.velocity[index];
479            let mut separation = Vec3A::ZERO;
480            let mut heading_sum = Vec3A::ZERO;
481            let mut position_sum = Vec3A::ZERO;
482            let mut neighbors = 0u32;
483
484            for near in around {
485                for other in near.clone() {
486                    if other == index {
487                        continue;
488                    }
489                    let offset = position - self.position[other];
490                    let squared = offset.length_squared();
491                    if squared > NEIGHBOR_RADIUS * NEIGHBOR_RADIUS || squared <= f32::EPSILON {
492                        continue;
493                    }
494                    if squared < SEPARATION_RADIUS * SEPARATION_RADIUS {
495                        separation += offset / squared.sqrt();
496                    }
497                    heading_sum += self.velocity[other];
498                    position_sum += self.position[other];
499                    neighbors += 1;
500                }
501            }
502
503            let mut steering = separation * SEPARATION_WEIGHT;
504            if neighbors > 0 {
505                let share = 1.0 / neighbors as f32;
506                steering += (heading_sum * share - velocity) * ALIGNMENT_WEIGHT
507                    + (position_sum * share - position) * COHESION_WEIGHT;
508            }
509            let from_center = position - world.center;
510            if from_center.length() > world.radius {
511                steering -= from_center.normalize() * BOUND_WEIGHT;
512            }
513
514            let next = velocity + steering * dt;
515            let speed = next.length().clamp(MIN_SPEED, MAX_SPEED);
516            let next_velocity = next.normalize_or_zero() * speed;
517            let next_position = position + next_velocity * dt;
518            out.position[at] = next_position;
519            out.velocity[at] = next_velocity;
520            out.kind[at] = self.kind[index];
521            out.cell[at] = self.cells.of(next_position) as u32;
522        }
523    }
524}
525
526/// One cell's share of the next arrays, written by that cell's step alone.
527struct CellOut<'a> {
528    position: &'a mut [Vec3A],
529    velocity: &'a mut [Vec3A],
530    kind: &'a mut [Kind],
531    cell: &'a mut [u32],
532}
533
534impl<'a> CellOut<'a> {
535    /// The next arrays split into one share per cell of `flock`, in cell
536    /// order, each as long as that cell's range.
537    fn split(
538        flock: &Flying<'_>,
539        position: &'a mut Vec<Vec3A>,
540        velocity: &'a mut Vec<Vec3A>,
541        kind: &'a mut Vec<Kind>,
542        cell: &'a mut Vec<u32>,
543    ) -> Vec<Self> {
544        let count = flock.position.len();
545        position.resize(count, Vec3A::ZERO);
546        velocity.resize(count, Vec3A::ZERO);
547        kind.resize(count, Kind::default());
548        cell.resize(count, 0);
549        let mut out = Vec::with_capacity(flock.cells.count());
550        let mut position = position.as_mut_slice();
551        let mut velocity = velocity.as_mut_slice();
552        let mut kind = kind.as_mut_slice();
553        let mut cell = cell.as_mut_slice();
554        for at in 0..flock.cells.count() {
555            let len = flock.range(at).len();
556            let (own, rest) = position.split_at_mut(len);
557            position = rest;
558            let (own_velocity, rest) = velocity.split_at_mut(len);
559            velocity = rest;
560            let (own_kind, rest) = kind.split_at_mut(len);
561            kind = rest;
562            let (own_cell, rest) = cell.split_at_mut(len);
563            cell = rest;
564            out.push(Self {
565                position: own,
566                velocity: own_velocity,
567                kind: own_kind,
568                cell: own_cell,
569            });
570        }
571        out
572    }
573}
574
575/// An integer-hash of `seed` and `salt`.
576fn hash(seed: u32, salt: u32) -> u32 {
577    let mut x = seed ^ salt.wrapping_mul(0x9E37_79B9);
578    x ^= x >> 16;
579    x = x.wrapping_mul(0x7FEB_352D);
580    x ^= x >> 15;
581    x = x.wrapping_mul(0x846C_A68B);
582    x ^= x >> 16;
583    x
584}
585
586/// `hash`, scaled to `0.0..1.0`.
587fn hash_unit(seed: u32, salt: u32) -> f32 {
588    hash(seed, salt) as f32 / u32::MAX as f32
589}
590
591/// The load a player chooses: how many butterflies the flock holds, and whether
592/// the step below runs one cell after another instead of on the engine's
593/// workers.
594struct Settings {
595    flock_size: u32,
596    sequential: bool,
597}
598
599impl Default for Settings {
600    fn default() -> Self {
601        Self {
602            flock_size: DEFAULT_FLOCK_SIZE,
603            sequential: false,
604        }
605    }
606}
607
608struct Flock {
609    settings: Settings,
610    applied_flock_size: u32,
611    world: World,
612    butterflies: Butterflies,
613    flaps: [Animator<Butterfly, FlapState>; FLAP_GROUPS],
614    /// Seconds the ticks have run, which paces the flap.
615    flown: f32,
616    last_tick_ms: f32,
617}
618
619impl Flock {
620    fn init(_ctx: &mut InitContext<'_, Self>) -> Result<Self, Error> {
621        let settings = Settings::default();
622        let world = World::for_flock(settings.flock_size);
623        Ok(Self {
624            applied_flock_size: settings.flock_size,
625            butterflies: Butterflies::scattered(settings.flock_size, world),
626            settings,
627            world,
628            flaps: core::array::from_fn(|_| Animator::new()),
629            flown: 0.0,
630            last_tick_ms: 0.0,
631        })
632    }
633
634    /// Rebuilds the flock where the chosen size changed since the last
635    /// frame.
636    fn apply_settings(&mut self) {
637        if self.settings.flock_size == self.applied_flock_size {
638            return;
639        }
640        self.world = World::for_flock(self.settings.flock_size);
641        self.butterflies = Butterflies::scattered(self.settings.flock_size, self.world);
642        self.applied_flock_size = self.settings.flock_size;
643    }
644
645    /// The camera at `elapsed`, turning about `center` at
646    /// [`CAMERA_HEIGHT_FRACTION`] and [`CAMERA_DISTANCE_FRACTION`] of the
647    /// flock's own radius, looking [`CAMERA_AIM_LIFT_FRACTION`] above it.
648    fn camera(center: Vec3, world: World, elapsed: f32) -> Camera {
649        let angle = elapsed * CAMERA_ANGULAR_SPEED;
650        let eye = center
651            + Vec3::new(
652                angle.cos() * world.radius * CAMERA_DISTANCE_FRACTION,
653                world.radius * CAMERA_HEIGHT_FRACTION,
654                angle.sin() * world.radius * CAMERA_DISTANCE_FRACTION,
655            );
656        let aim = center + Vec3::Y * world.radius * CAMERA_AIM_LIFT_FRACTION;
657        Camera::new(View::look_at(eye, aim), Projection::perspective(CAMERA_FOV))
658    }
659
660    fn draw_ground(ctx: &mut FrameContext<'_, Self>) {
661        ctx.draw(
662            Plane
663                .at(Transform::from_scale(Vec3::new(
664                    GROUND_SIZE,
665                    1.0,
666                    GROUND_SIZE,
667                )))
668                .material(Material::lit(GROUND_COLOR).roughness(0.9)),
669        );
670    }
Source

pub const fn splat(v: f32) -> Vec3

Creates a vector with all elements set to v.

Examples found in repository?
examples/isometric-board.rs (line 44)
44const BLOCK_HALF_EXTENTS: Vec3 = Vec3::splat(BLOCK_SIZE * 0.5);
45
46/// The name `assets.texture` pulls the sheet under, once loaded.
47const SPRITE_TEXTURE: &str = "walker";
48const SPRITE_SOURCE: &str = "examples/assets/walker.png";
49const CLICK_SOURCE: &str = "examples/assets/click.ogg";
50
51/// The texture's grid: rows top to bottom are toward, right, away, and
52/// left; four frames of a walk cycle across each row. The sprite unit
53/// draws the right row always, mirrored across its own width for a left
54/// order, in place of a left row of its own.
55const SPRITE_COLUMNS: u32 = 4;
56const SPRITE_ROWS: u32 = 4;
57const SPRITE_ROW: u32 = 1;
58const SPRITE_COLUMN: u32 = 0;
59
60/// Each rock around the board: position, a `seed` for its mesh, and a
61/// scale for the transform that places it.
62const ROCKS: [(f32, f32, u32, f32); 5] = [
63    (-BOARD_HALF - 1.2, -BOARD_HALF - 0.6, 11, 1.0),
64    (-BOARD_HALF - 0.8, BOARD_HALF + 1.0, 37, 0.8),
65    (BOARD_HALF + 1.4, -BOARD_HALF - 0.2, 58, 1.3),
66    (BOARD_HALF + 1.0, BOARD_HALF + 1.2, 71, 0.9),
67    (0.3, BOARD_HALF + 1.6, 94, 1.1),
68];
69
70/// How far a rock's corner is displaced from its position on a unit
71/// cube, on each axis.
72const ROCK_JITTER: f32 = 0.16;
73
74/// Half the ground's width and depth under the board and its rock ring,
75/// in meters.
76const GROUND_HALF: f32 = BOARD_HALF + 3.0;
77
78/// The ground's surface height, just under the board's own tiles, clear
79/// of a z-fighting seam with them.
80const GROUND_Y: f32 = -0.01;
81
82const LIGHT_TILE: Color = Color::rgb(0.80, 0.76, 0.64);
83const DARK_TILE: Color = Color::rgb(0.55, 0.50, 0.40);
84/// A reachable tile's own mark, smaller than the tile itself so the
85/// checker tone still shows around its edge.
86const REACHABLE_MARK: Color = Color::rgb(0.20, 0.85, 0.35);
87/// The fraction of a tile's own footprint the reachable mark draws at,
88/// small enough that the tile's own checker tone still shows around it.
89const REACHABLE_MARK_SCALE: f32 = 0.45;
90/// The reachable mark's own lift over the tile's surface, clear of
91/// z-fighting with it.
92const REACHABLE_MARK_LIFT: f32 = 0.01;
93/// The mark under the selected unit, its own color bright enough to read
94/// past the sprite's own tint at a distance.
95const CURRENT_MARK: Color = Color::rgb(1.0, 0.2, 0.75);
96const CURRENT_MARK_SCALE: f32 = 0.85;
97/// The mark under the unit whose turn it is while nothing is selected:
98/// smaller and dim next to [`CURRENT_MARK`], a hint rather than a claim.
99const TURN_MARK: Color = Color::rgb(0.85, 0.75, 0.15);
100const TURN_MARK_SCALE: f32 = 0.5;
101/// The tile a hover reads while a unit is selected: reachable, or blocked
102/// by the other unit standing there.
103const HOVER_REACHABLE_TILE: Color = Color::rgb(0.35, 0.75, 0.68);
104const HOVER_BLOCKED_TILE: Color = Color::rgb(0.62, 0.28, 0.26);
105const BLOCK_IDLE: Color = Color::rgb(0.32, 0.42, 0.58);
106/// `BLOCK_IDLE`, scaled toward white to mark the block unit's own turn.
107const BLOCK_TURN: Color = Color::rgb(0.42, 0.54, 0.72);
108const GROUND_COLOR: Color = Color::rgb(0.15, 0.16, 0.13);
109const ROCK_COLOR: Color = Color::rgb(0.42, 0.40, 0.38);
110const SUN_COLOR: Color = Color::rgb(0.95, 0.92, 0.85);
111
112/// The current unit's tint, close to white so the sprite's own texture
113/// still reads under it, and the light it adds on its own, low enough
114/// that the same texture still reads under its bloom too — distinct from
115/// `SELECTED_TINT`, so a hover and a selection never read the same.
116const HOVER_TINT: Color = Color::rgb(0.9, 1.15, 1.15);
117const HOVER_GLOW: Color = Color::rgb(0.02, 0.15, 0.2);
118
119/// The current unit's tint, close to white with more red where
120/// `HOVER_TINT` raises green and blue instead, so the sprite's own
121/// texture still reads under it, and the light it adds on its own,
122/// scaled down the same way `HOVER_GLOW` is — distinct from `HOVER_TINT`.
123const SELECTED_TINT: Color = Color::rgb(1.15, 0.95, 0.85);
124const SELECTED_GLOW: Color = Color::rgb(0.22, 0.11, 0.0);
125
126/// The unit whose turn it is shows this tint and glow before any hover or
127/// selection, so it reads as the one a click selects.
128const TURN_TINT: Color = Color::rgb(1.0, 1.0, 0.82);
129const TURN_GLOW: Color = Color::rgb(0.08, 0.08, 0.02);
130
131/// The fraction of the frame `set_bloom` spreads, so `HOVER_GLOW` and
132/// `SELECTED_GLOW` read as light around the current unit, not only a
133/// larger fill.
134const BLOOM: f32 = 0.35;
135
136/// The size a world-space prompt naming a click's effect reads at, in
137/// logical points.
138const PROMPT_SIZE: f32 = 15.0;
139/// Height a prompt is lifted over the tile it names, clear of the tile's
140/// own top corner under the diagonal view.
141const PROMPT_TILE_LIFT: f32 = 0.55;
142/// Height a prompt is lifted over the unit it names, past its own height.
143const PROMPT_UNIT_LIFT: f32 = 0.25;
144/// Margin a prompt's own backdrop keeps past its galley, in logical points.
145const PROMPT_PADDING: f32 = 4.0;
146/// How much dark a prompt's own backdrop puts behind its text.
147const PROMPT_BACKDROP: u8 = 190;
148const PROMPT_TEXT_COLOR: egui::Color32 = egui::Color32::from_gray(230);
149
150/// Thirty steps a second, half the engine's default rate; movement stays
151/// smooth through `alpha()` interpolation.
152const TICK_INTERVAL: Duration = Duration::from_nanos(33_333_333);
153
154/// Faces of a cube, each a normal with its right and up axes — the same
155/// layout `mesh::Cube` builds from, shared so a rock's corners hold the
156/// same eight positions between the faces that meet there.
157const ROCK_FACES: [(Vec3, Vec3, Vec3); 6] = [
158    (Vec3::X, Vec3::NEG_Z, Vec3::Y),
159    (Vec3::NEG_X, Vec3::Z, Vec3::Y),
160    (Vec3::Y, Vec3::X, Vec3::NEG_Z),
161    (Vec3::NEG_Y, Vec3::X, Vec3::Z),
162    (Vec3::Z, Vec3::X, Vec3::Y),
163    (Vec3::NEG_Z, Vec3::NEG_X, Vec3::Y),
164];
165
166const ROCK_TRIANGLES: [u32; 6] = [0, 1, 2, 0, 2, 3];
167
168fn main() {
169    run(
170        Config::new("Mirage: isometric board")
171            .with_size(1280, 720)
172            .with_assets([SPRITE_SOURCE, CLICK_SOURCE])
173            .with_tick_interval(TICK_INTERVAL),
174        Board::init,
175    );
176}
177
178/// A rock built for its own `seed`; each value is its own mesh.
179#[derive(Catalog, Clone, PartialEq, Eq, Hash)]
180#[catalog(Self { seed: 0 })]
181struct Rock {
182    seed: u32,
183}
184
185impl Mesh for Rock {
186    fn build(&self, _: &Assets) -> MeshData {
187        build_rock(self.seed)
188    }
189}
190
191/// The sprite unit: a quad windowed to the walk sheet's row facing right.
192#[derive(Catalog, Clone, PartialEq, Eq, Hash)]
193struct Sprite;
194
195impl Mesh for Sprite {
196    fn build(&self, assets: &Assets) -> MeshData {
197        Quad.build(assets)
198            .with_texture(assets.texture(SPRITE_TEXTURE).pixelated())
199    }
200}
201
202// Everything else this game draws: the ground and board tiles are the
203// engine's own Plane and Cube, given their color per draw; the block unit
204// draws as a plain Cube too.
205meshes! { enum Shape { Plane, Cube, Rock, Sprite } }
206
207/// The board's own sky: a dim gradient, so the sun stays the scene's
208/// brightest light.
209#[derive(Catalog, Clone, Copy, Debug, PartialEq, Eq, Hash)]
210enum Sky {
211    Day,
212}
213
214impl Skyboxes for Sky {
215    fn build(&self, _assets: &Assets) -> SkyboxData {
216        SkyboxData::gradient(
217            Color::rgb(0.55, 0.75, 0.95),
218            Color::rgb(0.85, 0.90, 0.95),
219            Color::rgb(0.35, 0.33, 0.30),
220        )
221        .lit_by(0.3)
222    }
223}
224
225/// The one sound this game plays.
226#[derive(Catalog, Clone, PartialEq, Eq, Hash)]
227enum Sound {
228    Click,
229}
230
231impl Sounds for Sound {
232    fn build(&self, assets: &Assets) -> SoundData {
233        match self {
234            Sound::Click => assets.sound("click"),
235        }
236    }
237}
238
239/// The one control this game reads: a click, which selects the unit whose
240/// turn it is or orders it to a tile.
241#[derive(InputButtonAction, Clone, Copy)]
242enum Button {
243    Select,
244}
245
246impl InputButtonAction for Button {
247    fn bindings(&self) -> Vec<ButtonBinding> {
248        match self {
249            Button::Select => vec![MouseButton::Left.into()],
250        }
251    }
252}
253
254struct Controls;
255
256impl InputActions for Controls {
257    type Button = Button;
258    type Axis = NoInputAxes;
259    type Axis2 = NoInputAxes2;
260}
261
262/// Whose turn it is: `Sprite` draws the unit with a texture, `Block` the
263/// plain one.
264#[derive(Clone, Copy, PartialEq, Eq)]
265enum Turn {
266    Sprite,
267    Block,
268}
269
270impl Turn {
271    fn other(self) -> Self {
272        match self {
273            Turn::Sprite => Turn::Block,
274            Turn::Block => Turn::Sprite,
275        }
276    }
277}
278
279/// One unit's position on the board.
280struct Unit {
281    tile: (i32, i32),
282    position: Vec3,
283    previous: Vec3,
284    target: Option<Vec3>,
285    /// The order the unit last moved along `+X` under, kept while it
286    /// stays still.
287    facing_right: bool,
288}
289
290impl Unit {
291    fn resting(tile: (i32, i32), lift: f32) -> Self {
292        let position = tile_center(tile) + Vec3::Y * lift;
293        Self {
294            tile,
295            position,
296            previous: position,
297            target: None,
298            facing_right: true,
299        }
300    }
301
302    /// Steps the unit toward its ordered position by one tick's distance;
303    /// returns whether it landed on the position this tick.
304    fn advance(&mut self, dt: Duration) -> bool {
305        let Some(target) = self.target else {
306            return false;
307        };
308        let to_target = target - self.position;
309        let distance = to_target.length();
310        let step = UNIT_SPEED * dt.as_secs_f32();
311        if distance <= step {
312            self.position = target;
313            self.target = None;
314            true
315        } else {
316            self.position += to_target * (step / distance);
317            false
318        }
319    }
320}
321
322/// The vertical lift from a tile's surface to a unit's center, and the
323/// half-extents `Ray::hit_aabb` reads its box by.
324fn unit_geometry(turn: Turn) -> (f32, Vec3) {
325    match turn {
326        Turn::Sprite => (SPRITE_HEIGHT * 0.5, SPRITE_HALF_EXTENTS),
327        Turn::Block => (BLOCK_SIZE * 0.5, BLOCK_HALF_EXTENTS),
328    }
329}
330
331/// The world position a tile's center is at, on the board's surface
332/// plane.
333fn tile_center((col, row): (i32, i32)) -> Vec3 {
334    let half = (BOARD_TILES - 1) as f32 * 0.5;
335    Vec3::new(
336        (col as f32 - half) * TILE_SIZE,
337        0.0,
338        (row as f32 - half) * TILE_SIZE,
339    )
340}
341
342/// The tile `point` falls over, ignoring its height; `None` off the board.
343fn tile_at(point: Vec3) -> Option<(i32, i32)> {
344    let half = (BOARD_TILES - 1) as f32 * 0.5;
345    let col = (point.x / TILE_SIZE + half + 0.5).floor() as i32;
346    let row = (point.z / TILE_SIZE + half + 0.5).floor() as i32;
347    ((0..BOARD_TILES).contains(&col) && (0..BOARD_TILES).contains(&row)).then_some((col, row))
348}
349
350/// Cursor target this frame: nothing, the unit whose turn it is, or a
351/// board tile.
352#[derive(Clone, Copy, PartialEq)]
353enum Hover {
354    None,
355    CurrentUnit,
356    Tile((i32, i32)),
357}
358
359struct Board {
360    sprite: Unit,
361    block: Unit,
362    turn: Turn,
363    selected: bool,
364}
365
366impl Board {
367    fn init(ctx: &mut InitContext<'_, Board>) -> Result<Self, Error> {
368        for &(_, _, seed, _) in &ROCKS {
369            ctx.prepare(Rock { seed });
370        }
371
372        let (sprite_lift, _) = unit_geometry(Turn::Sprite);
373        let (block_lift, _) = unit_geometry(Turn::Block);
374        Ok(Self {
375            sprite: Unit::resting((1, 1), sprite_lift),
376            block: Unit::resting((BOARD_TILES - 2, BOARD_TILES - 2), block_lift),
377            turn: Turn::Sprite,
378            selected: false,
379        })
380    }
381
382    fn camera() -> Camera {
383        let eye = Vec3::new(9.0, 9.0, 9.0);
384        Camera::new(
385            View::look_at(eye, Vec3::ZERO),
386            Projection::orthographic(11.0),
387        )
388    }
389
390    fn current(&self) -> &Unit {
391        match self.turn {
392            Turn::Sprite => &self.sprite,
393            Turn::Block => &self.block,
394        }
395    }
396
397    fn current_mut(&mut self) -> &mut Unit {
398        match self.turn {
399            Turn::Sprite => &mut self.sprite,
400            Turn::Block => &mut self.block,
401        }
402    }
403
404    fn other(&self) -> &Unit {
405        match self.turn {
406            Turn::Sprite => &self.block,
407            Turn::Block => &self.sprite,
408        }
409    }
410
411    /// Resolves a left click: hitting the current unit's box toggles its
412    /// selection; while selected, a ground hit that lands on an open tile
413    /// orders a move there.
414    fn handle_click(&mut self, ctx: &mut TickContext<'_, Board>) {
415        if ctx.ui_wants_pointer() || !ctx.pressed(Button::Select) {
416            return;
417        }
418        let ray = ctx
419            .last_camera()
420            .ray_through(ctx.pointer(), ctx.window_size());
421        let (lift, half) = unit_geometry(self.turn);
422
423        if self.current().target.is_none() {
424            let center = self.current().position;
425            if ray.hit_aabb(center - half, center + half).is_some() {
426                self.selected = !self.selected;
427                return;
428            }
429        }
430        if !self.selected {
431            return;
432        }
433
434        let Some(distance) = ray.hit_plane(ray::Plane {
435            point: Vec3::ZERO,
436            normal: Vec3::Y,
437        }) else {
438            return;
439        };
440        let Some(tile) = tile_at(ray.at(distance)) else {
441            return;
442        };
443        if tile == self.current().tile || tile == self.other().tile {
444            return;
445        }
446
447        let destination = tile_center(tile) + Vec3::Y * lift;
448        let heading = destination.x - self.current().position.x;
449        let current = self.current_mut();
450        if heading.abs() > f32::EPSILON {
451            current.facing_right = heading > 0.0;
452        }
453        current.target = Some(destination);
454        self.selected = false;
455        ctx.play(Sound::Click);
456    }
457
458    /// Cursor target, `None` while the UI has the pointer.
459    fn hovered(&self, ctx: &FrameContext<'_, Board>) -> Hover {
460        if ctx.ui_wants_pointer() {
461            return Hover::None;
462        }
463        let ray = ctx
464            .last_camera()
465            .ray_through(ctx.pointer(), ctx.window_size());
466
467        if self.current().target.is_none() {
468            let (_, half) = unit_geometry(self.turn);
469            let center = self.current().position;
470            if ray.hit_aabb(center - half, center + half).is_some() {
471                return Hover::CurrentUnit;
472            }
473        }
474        let Some(distance) = ray.hit_plane(ray::Plane {
475            point: Vec3::ZERO,
476            normal: Vec3::Y,
477        }) else {
478            return Hover::None;
479        };
480        match tile_at(ray.at(distance)) {
481            Some(tile) => Hover::Tile(tile),
482            None => Hover::None,
483        }
484    }
485
486    /// Whether a selected unit could move to `tile`: on the board, and
487    /// standing under neither unit.
488    fn reachable(&self, tile: (i32, i32)) -> bool {
489        tile != self.current().tile && tile != self.other().tile
490    }
491
492    fn draw_board(&self, ctx: &mut FrameContext<'_, Board>, hover: Hover) {
493        let scale = Vec3::new(TILE_SIZE - TILE_GAP, TILE_THICKNESS, TILE_SIZE - TILE_GAP);
494        for col in 0..BOARD_TILES {
495            for row in 0..BOARD_TILES {
496                let tile = (col, row);
497                let center = tile_center(tile) - Vec3::Y * (TILE_THICKNESS * 0.5);
498                let reachable = self.selected && self.reachable(tile);
499                let hovered = self.selected && hover == Hover::Tile(tile);
500                let color = if hovered {
501                    if reachable {
502                        HOVER_REACHABLE_TILE
503                    } else {
504                        HOVER_BLOCKED_TILE
505                    }
506                } else if (col + row) % 2 == 0 {
507                    LIGHT_TILE
508                } else {
509                    DARK_TILE
510                };
511                ctx.draw(
512                    Cube.at(Transform::from_scale_rotation_translation(
513                        scale,
514                        Quat::IDENTITY,
515                        center,
516                    ))
517                    .material(Material::lit(color)),
518                );
519                if reachable && !hovered {
520                    self.draw_reachable_mark(ctx, tile);
521                }
522            }
523        }
524    }
525
526    /// A mark over a reachable tile, its own tone apart from the
527    /// checker's tone and the hover tone, so the checker still reads
528    /// under it.
529    fn draw_reachable_mark(&self, ctx: &mut FrameContext<'_, Board>, tile: (i32, i32)) {
530        let center = tile_center(tile) + Vec3::Y * REACHABLE_MARK_LIFT;
531        ctx.draw(
532            Plane
533                .at(Transform::from_scale_rotation_translation(
534                    Vec3::new(
535                        TILE_SIZE * REACHABLE_MARK_SCALE,
536                        1.0,
537                        TILE_SIZE * REACHABLE_MARK_SCALE,
538                    ),
539                    Quat::IDENTITY,
540                    center,
541                ))
542                .material(Material::color(REACHABLE_MARK)),
543        );
544    }
545
546    /// A mark bright enough to read past the sprite's own tint under the
547    /// selected unit, or a smaller, dim one under the unit whose turn it
548    /// is while nothing is selected — so the current unit reads from the
549    /// ground alone.
550    fn draw_current_mark(&self, ctx: &mut FrameContext<'_, Board>) {
551        let (color, scale) = if self.selected {
552            (CURRENT_MARK, CURRENT_MARK_SCALE)
553        } else {
554            (TURN_MARK, TURN_MARK_SCALE)
555        };
556        let center = tile_center(self.current().tile) + Vec3::Y * REACHABLE_MARK_LIFT;
557        ctx.draw(
558            Plane
559                .at(Transform::from_scale_rotation_translation(
560                    Vec3::new(TILE_SIZE * scale, 1.0, TILE_SIZE * scale),
561                    Quat::IDENTITY,
562                    center,
563                ))
564                .material(Material::color(color)),
565        );
566    }
567
568    fn draw_rocks(&self, ctx: &mut FrameContext<'_, Board>) {
569        for &(x, z, seed, scale) in &ROCKS {
570            let angle = hash_signed(seed, 99) * core::f32::consts::PI;
571            ctx.draw(
572                Rock { seed }
573                    .at(Transform::from_scale_rotation_translation(
574                        Vec3::splat(scale),
575                        Quat::from_rotation_y(angle),
576                        Vec3::new(x, 0.5 * scale, z),
577                    ))
578                    .material(Material::lit(ROCK_COLOR)),
579            );
580        }
581    }
582
583    fn draw_sprite(&self, ctx: &mut FrameContext<'_, Board>, hover: Hover) {
584        let position = self.sprite.previous.lerp(self.sprite.position, ctx.alpha());
585        let current = self.turn == Turn::Sprite;
586        let (tint, glow) = if current && self.selected {
587            (SELECTED_TINT, SELECTED_GLOW)
588        } else if current && hover == Hover::CurrentUnit {
589            (HOVER_TINT, HOVER_GLOW)
590        } else if current {
591            (TURN_TINT, TURN_GLOW)
592        } else {
593            (Color::WHITE, Color::BLACK)
594        };
595        ctx.draw(
596            Sprite
597                .at(Transform::from_scale_rotation_translation(
598                    Vec3::new(SPRITE_WIDTH, SPRITE_HEIGHT, 1.0),
599                    Quat::IDENTITY,
600                    position,
601                ))
602                .upright()
603                .frame(sprite_frame(self.sprite.facing_right))
604                .material(Material::lit(tint).cutout().emissive(glow)),
605        );
606    }
607
608    fn draw_block(&self, ctx: &mut FrameContext<'_, Board>, hover: Hover) {
609        let position = self.block.previous.lerp(self.block.position, ctx.alpha());
610        let current = self.turn == Turn::Block;
611        let (color, glow) = if current && self.selected {
612            (SELECTED_TINT, SELECTED_GLOW)
613        } else if current && hover == Hover::CurrentUnit {
614            (HOVER_TINT, HOVER_GLOW)
615        } else if current {
616            (BLOCK_TURN, TURN_GLOW)
617        } else {
618            (BLOCK_IDLE, Color::BLACK)
619        };
620        ctx.draw(
621            Cube.at(Transform::from_scale_rotation_translation(
622                Vec3::splat(BLOCK_SIZE),
623                Quat::IDENTITY,
624                position,
625            ))
626            .material(Material::lit(color).emissive(glow)),
627        );
628    }
More examples
Hide additional examples
examples/flock-parallelism.rs (line 680)
674    fn draw_butterflies(&self, ctx: &mut FrameContext<'_, Self>) {
675        for (position, velocity, kind) in self.butterflies.each() {
676            let rotation = Quat::from_rotation_arc(Vec3::Z, Vec3::from(velocity).normalize());
677            ctx.draw(
678                Butterfly
679                    .at(Transform::from_scale_rotation_translation(
680                        Vec3::splat(BUTTERFLY_SCALE),
681                        rotation,
682                        Vec3::from(position),
683                    ))
684                    .posed(&self.flaps[usize::from(kind.flap)])
685                    .material(Material::lit(TINTS[usize::from(kind.tint)])),
686            );
687        }
688    }
examples/sprite-adventure.rs (line 1301)
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    }
examples/breakout-game.rs (line 668)
661    fn draw_sparks(&self, ctx: &mut FrameContext<'_, Breakout>) {
662        for spark in &self.sparks {
663            let age = (spark.age / SPARK_LIFETIME).clamp(0.0, 1.0);
664            let fade = 1.0 - age;
665            let size = SPARK_SIZE_START.lerp(SPARK_SIZE_END, age);
666            ctx.draw(
667                Quad.at(Transform::from_scale_rotation_translation(
668                    Vec3::splat(size),
669                    Quat::IDENTITY,
670                    spark.position,
671                ))
672                .billboard()
673                .roll(spark.roll + spark.age * SPARK_SPIN_SPEED)
674                .material(
675                    Material::color(spark.color.with_alpha(fade))
676                        .emissive(spark.color.dimmed(SPARK_EMISSIVE_PEAK))
677                        .additive(),
678                ),
679            );
680        }
681    }
682
683    /// Draws the ball's ghost trail, each ghost smaller and more transparent
684    /// than the one ahead of it; each ghost's position interpolates between
685    /// its own last two resolved ticks by the same `alpha` the ball itself
686    /// draws at, and its radius clamps to what the ball's own radius has
687    /// left over its distance from the head, so a ghost still close to the
688    /// ball never draws past its edge.
689    fn draw_trail(&self, ctx: &mut FrameContext<'_, Breakout>, alpha: f32) {
690        let head = self.ball_trail[1].lerp(self.ball_trail[0], alpha);
691        for i in 0..TRAIL_LEN {
692            let position = self.ball_trail[i + 1].lerp(self.ball_trail[i], alpha);
693            let age = (i + 1) as f32 / TRAIL_LEN as f32;
694            let fade = (1.0 - age).max(TRAIL_ALPHA_FLOOR);
695            let radius = (BALL_RADIUS * TRAIL_SCALE_MIN.lerp(TRAIL_SCALE_MAX, fade))
696                .min((BALL_RADIUS - head.distance(position)).max(0.0));
697            let scale = Vec3::splat(radius * 2.0);
698            ctx.draw(
699                Sphere { subdivisions: 2 }
700                    .at(Transform::from_scale_rotation_translation(
701                        scale,
702                        Quat::IDENTITY,
703                        position,
704                    ))
705                    .material(
706                        Material::color(BALL_GLOW.with_alpha(fade))
707                            .emissive(BALL_EMISSIVE.dimmed(TRAIL_EMISSIVE_PEAK)),
708                    ),
709            );
710        }
711    }
712
713    /// Draws one held ball for every life past the one in play, set in a
714    /// row alongside the paddle's own path.
715    fn draw_lives(&self, ctx: &mut FrameContext<'_, Breakout>) {
716        let held_lives = self.lives.saturating_sub(1);
717        for slot in 0..held_lives {
718            let z = PADDLE_Z + (slot + 1) as f32 * LIFE_ROW_SPACING;
719            ctx.draw(
720                Sphere { subdivisions: 2 }
721                    .at(Transform::from_scale_rotation_translation(
722                        Vec3::splat(BALL_RADIUS * 2.0),
723                        Quat::IDENTITY,
724                        Vec3::new(LIFE_ROW_X, BALL_RADIUS, z),
725                    ))
726                    .material(
727                        Material::color(BALL_GLOW)
728                            .emissive(BALL_EMISSIVE)
729                            .additive(),
730                    ),
731            );
732        }
733    }
734
735    fn overlay(&mut self, ctx: &mut FrameContext<'_, Breakout>) {
736        let bricks_left = self
737            .bricks
738            .iter()
739            .filter(|brick| brick.hits_remaining > 0)
740            .count();
741        // Read before `ctx.ui` so a rebind changes what the hint reads this
742        // frame too.
743        let move_hint = bindings_text(ctx.bindings(Move::Paddle));
744        let pause_hint = bindings_text(ctx.bindings(Button::Pause));
745        let serve_hint = bindings_text(ctx.bindings(Button::Serve));
746        ctx.ui(|ui| {
747            ui.horizontal(|ui| {
748                ui.label(egui::RichText::new(format!("score {}", self.score)).size(32.0));
749                ui.label(format!("{bricks_left} bricks left"));
750            });
751            ui.label(format!("move: {move_hint} · {pause_hint} to pause"));
752            if self.phase == Phase::Serving {
753                ui.label(format!("{serve_hint} to serve"));
754            }
755        });
756
757        match self.phase {
758            Phase::Serving | Phase::Playing if self.paused => self.menu(ctx, "paused", false),
759            Phase::Won => self.menu(ctx, "you win", true),
760            Phase::Lost => self.menu(ctx, "game over", true),
761            _ => {}
762        }
763    }
764
765    fn menu(&mut self, ctx: &mut FrameContext<'_, Breakout>, title: &str, over: bool) {
766        let mut clicked = false;
767        let mut quit = false;
768
769        // `ctx.ui` cannot borrow `ctx`, so anything the controls list needs is
770        // read first and applied after.
771        let buttons: Vec<(Button, String)> = Button::all()
772            .into_iter()
773            .map(|action| (action, bindings_text(ctx.bindings(action))))
774            .collect();
775        let axes: Vec<(Move, String)> = Move::all()
776            .into_iter()
777            .map(|action| (action, bindings_text(ctx.bindings(action))))
778            .collect();
779        let listening = self.listening;
780        let actuated_button = (!ctx.ui_wants_keyboard())
781            .then(|| ctx.actuated_button())
782            .flatten();
783        let actuated_axis = (!ctx.ui_wants_keyboard())
784            .then(|| ctx.actuated_axis())
785            .flatten();
786        let mut reset = None;
787
788        ctx.ui(|ui| {
789            egui::Window::new(title)
790                .collapsible(false)
791                .resizable(false)
792                .anchor(egui::Align2::CENTER_CENTER, egui::Vec2::ZERO)
793                .show(ui.ctx(), |ui| {
794                    if over {
795                        ui.label(format!("score {}", self.score));
796                    }
797                    if !over {
798                        ui.add(
799                            egui::Slider::new(&mut self.master_volume, 0.0..=1.0).text("volume"),
800                        );
801                        if ui.button("resume").clicked() {
802                            self.paused = false;
803                            clicked = true;
804                        }
805                        ui.separator();
806                        ui.heading("controls");
807                        for (action, text) in &buttons {
808                            controls_row(
809                                ui,
810                                action.name(),
811                                text,
812                                listening == Some(Listening::Button(*action)),
813                                &mut self.listening,
814                                Listening::Button(*action),
815                                &mut reset,
816                            );
817                        }
818                        for (action, text) in &axes {
819                            controls_row(
820                                ui,
821                                action.name(),
822                                text,
823                                listening == Some(Listening::Move(*action)),
824                                &mut self.listening,
825                                Listening::Move(*action),
826                                &mut reset,
827                            );
828                        }
829                    }
830                    if ui.button("restart").clicked() {
831                        self.restart();
832                        clicked = true;
833                    }
834                    if ui.button("quit").clicked() {
835                        quit = true;
836                    }
837                });
838        });
839
840        match (self.listening, actuated_button, actuated_axis) {
841            (Some(Listening::Button(action)), Some(binding), _) => {
842                ctx.rebind(action, vec![binding]);
843                self.listening = None;
844            }
845            (Some(Listening::Move(action)), _, Some(binding)) => {
846                ctx.rebind(action, vec![binding]);
847                self.listening = None;
848            }
849            _ => {}
850        }
851        match reset {
852            Some(Listening::Button(action)) => ctx.rebind(action, action.bindings()),
853            Some(Listening::Move(action)) => ctx.rebind(action, action.bindings()),
854            None => {}
855        }
856
857        if clicked {
858            ctx.play(Sound::Click);
859        }
860        if quit {
861            ctx.close();
862        }
863    }
864
865    /// Sustains both tracks every frame, and the gain goes to whichever the
866    /// game calls for: gameplay music while a round is live, serving
867    /// included, and menu music whenever a menu covers it.
868    ///
869    /// Each fades in over [`MUSIC_CROSSFADE`] and slides every later gain
870    /// over it, which is the crossfade itself; the one at no gain costs no
871    /// voice while its playback goes on under the other.
872    fn sustain_music(&self, ctx: &mut FrameContext<'_, Breakout>) {
873        let playing = !self.paused && matches!(self.phase, Phase::Serving | Phase::Playing);
874        let gain = |wanted: bool| match wanted {
875            true => MUSIC_GAIN,
876            false => 0.0,
877        };
878
879        ctx.sustain(
880            Sound::Music
881                .gain(gain(playing))
882                .fade(MUSIC_CROSSFADE)
883                .glide(MUSIC_CROSSFADE)
884                .loop_from(MUSIC_LOOP_FROM),
885        );
886        ctx.sustain(
887            Sound::MenuMusic
888                .gain(gain(!playing))
889                .fade(MUSIC_CROSSFADE)
890                .glide(MUSIC_CROSSFADE)
891                .loop_from(MENU_MUSIC_LOOP_FROM),
892        );
893    }
894}
895
896/// One action's name, its live bindings, a rebind control that starts
897/// listening for a new one, and a reset to its defaults; cancel is a
898/// button rather than Escape, since Escape is itself a binding a listen
899/// could capture.
900fn controls_row(
901    ui: &mut egui::Ui,
902    name: &str,
903    bindings: &str,
904    listening: bool,
905    target: &mut Option<Listening>,
906    action: Listening,
907    reset: &mut Option<Listening>,
908) {
909    ui.horizontal(|ui| {
910        ui.label(format!("{name}: {bindings}"));
911        if listening {
912            ui.label("listening");
913            if ui.button("cancel").clicked() {
914                *target = None;
915            }
916        } else if ui.button("rebind").clicked() {
917            *target = Some(action);
918        }
919        if ui.button("reset").clicked() {
920            *reset = Some(action);
921        }
922    });
923}
924
925/// The controls-menu text for a live binding list: each alternative,
926/// separated, in the order the player can use them.
927fn bindings_text<B: Display>(bindings: Vec<B>) -> String {
928    bindings
929        .iter()
930        .map(ToString::to_string)
931        .collect::<Vec<_>>()
932        .join(", ")
933}
934
935fn spawn_bricks() -> Vec<Brick> {
936    let cell = BRICK_HALF_WIDTH * 2.0 + BRICK_GAP;
937    let row_span = BRICK_HALF_DEPTH * 2.0 + BRICK_ROW_GAP;
938    let grid_width = cell * BRICK_COLUMNS as f32 - BRICK_GAP;
939    let start_x = -grid_width * 0.5 + BRICK_HALF_WIDTH;
940    let start_z = -COURT_HALF_DEPTH + WALL_THICKNESS + BRICK_HALF_DEPTH + 0.6;
941
942    (0..BRICK_ROWS)
943        .flat_map(|row| {
944            (0..BRICK_COLUMNS).map(move |column| Brick {
945                row,
946                position: Vec3::new(
947                    start_x + column as f32 * cell,
948                    BRICK_HALF_HEIGHT,
949                    start_z + row as f32 * row_span,
950                ),
951                hits_remaining: BRICK_HITS,
952            })
953        })
954        .collect()
955}
956
957impl Game for Breakout {
958    type Meshes = Shape;
959    type Sounds = Sound;
960    type InputActions = Controls;
961    type Skyboxes = NoSkyboxes;
962    type SurfaceStyles = NoSurfaceStyles;
963    type PostEffects = NoPostEffects;
964
965    fn tick(&mut self, ctx: &mut TickContext<'_, Breakout>) {
966        if self.paused {
967            return;
968        }
969
970        let dt = ctx.dt().as_secs_f32();
971        self.paddle_flash = (self.paddle_flash - dt).max(0.0);
972        self.brick_flash = (self.brick_flash - dt).max(0.0);
973        self.life_lost_flash = (self.life_lost_flash - dt).max(0.0);
974        self.step_sparks(dt);
975
976        // Decay runs before the end-screen return below, so the last pulse and
977        // burst do not stay on screen.
978        if matches!(self.phase, Phase::Won | Phase::Lost) {
979            return;
980        }
981
982        let axis = if ctx.ui_wants_keyboard() {
983            0.0
984        } else {
985            ctx.axis(Move::Paddle)
986        };
987        self.step_paddle(axis, dt);
988
989        match self.phase {
990            Phase::Serving => self.hold_ball(ctx),
991            _ => self.step_ball(ctx, dt),
992        }
993    }
994
995    fn frame(&mut self, ctx: &mut FrameContext<'_, Breakout>) {
996        if matches!(self.phase, Phase::Serving | Phase::Playing) && ctx.pressed(Button::Pause) {
997            self.paused = !self.paused;
998        }
999
1000        ctx.set_volume(self.master_volume);
1001        self.sustain_music(ctx);
1002
1003        ctx.set_camera(Self::camera());
1004
1005        let brick_pulse = (self.brick_flash / BRICK_FLASH).clamp(0.0, 1.0);
1006        ctx.set_bloom((BLOOM_BASE + brick_pulse * BLOOM_PULSE_PEAK).clamp(0.0, 1.0));
1007
1008        let life_lost_t = (self.life_lost_flash / LIFE_LOST_FLASH).clamp(0.0, 1.0);
1009        ctx.set_exposure((1.0 - life_lost_t * EXPOSURE_DIP_DEPTH).clamp(0.0, 1.0));
1010
1011        // The tick moves nothing behind a menu, so a frame there draws the last
1012        // step whole rather than interpolating from the one before.
1013        let alpha = match self.phase {
1014            Phase::Serving | Phase::Playing if !self.paused => ctx.alpha(),
1015            _ => 1.0,
1016        };
1017        let paddle_x = self.paddle_prev_x.lerp(self.paddle_x, alpha);
1018        let ball_pos = self.ball_prev.lerp(self.ball_pos, alpha);
1019
1020        ctx.light(Light::point(ball_pos, BALL_GLOW, BALL_LIGHT_RANGE).shadow());
1021
1022        self.draw_court(ctx);
1023        self.draw_bricks(ctx);
1024        self.draw_sparks(ctx);
1025        self.draw_lives(ctx);
1026
1027        ctx.draw(
1028            Paddle
1029                .at(Transform::from_translation(Vec3::new(
1030                    paddle_x,
1031                    PADDLE_HALF_HEIGHT,
1032                    PADDLE_Z,
1033                )))
1034                .material_of(PaddlePart::Face, self.paddle_face_material()),
1035        );
1036
1037        self.draw_trail(ctx, alpha);
1038        ctx.draw(
1039            Sphere { subdivisions: 2 }
1040                .at(Transform::from_scale_rotation_translation(
1041                    Vec3::splat(BALL_RADIUS * 2.0),
1042                    Quat::IDENTITY,
1043                    ball_pos,
1044                ))
1045                .material(
1046                    Material::color(BALL_GLOW)
1047                        .emissive(BALL_EMISSIVE)
1048                        .additive(),
1049                ),
1050        );
1051
1052        self.overlay(ctx);
1053    }
examples/post-effects.rs (line 104)
90    fn draw_scene(&self, ctx: &mut FrameContext<'_, Self>) {
91        ctx.light(Light::directional(SUN_DIRECTION, SUN_COLOR).shadow());
92
93        ctx.draw(
94            Plane
95                .at(Transform::from_scale(Vec3::new(
96                    GROUND_SIZE,
97                    1.0,
98                    GROUND_SIZE,
99                )))
100                .material(Material::lit(GROUND_COLOR)),
101        );
102        ctx.draw(
103            Cube.at(Transform::from_scale_rotation_translation(
104                Vec3::splat(GLOW_SIZE),
105                Quat::IDENTITY,
106                GLOW_POSITION,
107            ))
108            .material(Material::color(Color::BLACK).emissive(GLOW_COLOR)),
109        );
110        for position in SPHERE_POSITIONS {
111            ctx.draw(
112                Sphere {
113                    subdivisions: SPHERE_SUBDIVISIONS,
114                }
115                .at(position)
116                .material(Material::lit(SPHERE_COLOR)),
117            );
118        }
119    }
examples/sound-lab.rs (line 573)
547    fn draw_sources(&self, ctx: &mut FrameContext<'_, SoundCheck>) {
548        for (index, source) in self.sources.iter().enumerate() {
549            let color = SOURCE_COLORS[index];
550            let picked_up = self.dragging == Some(index);
551            let scale = if picked_up { 1.3 } else { 1.0 };
552            let emissive = if source.enabled {
553                Color::rgb(color.red * 3.0, color.green * 3.0, color.blue * 3.0)
554            } else {
555                color.dimmed(0.15)
556            };
557
558            for (radius, ring_color) in [
559                (source.range, RANGE_COLOR),
560                (source.reference, REFERENCE_COLOR),
561            ] {
562                ctx.draw(
563                    Ring.at(Transform::from_scale_rotation_translation(
564                        Vec3::new(radius, 1.0, radius),
565                        Quat::IDENTITY,
566                        Vec3::new(source.position.x, 0.01, source.position.z),
567                    ))
568                    .material(Material::color(ring_color)),
569                );
570            }
571            ctx.draw(
572                Cube.at(Transform::from_scale_rotation_translation(
573                    Vec3::splat(SOURCE_HALF * 2.0 * scale),
574                    Quat::IDENTITY,
575                    source.position,
576                ))
577                .material(Material::shaded(color, 0.6).emissive(emissive)),
578            );
579        }
580    }
581
582    /// The listener: a cube drawn from the ground up to [`EYE_HEIGHT`],
583    /// an ear pair set on ± `view`'s right, and a marker at the front that
584    /// shows its fixed `-Z` facing.
585    fn draw_listener(&self, ctx: &mut FrameContext<'_, SoundCheck>, view: View) {
586        let head = view.eye();
587        let ground = Vec3::new(head.x, 0.0, head.z);
588
589        ctx.draw(
590            Cube.at(Transform::from_scale_rotation_translation(
591                Vec3::new(LISTENER_WIDTH, head.y, LISTENER_DEPTH),
592                Quat::IDENTITY,
593                ground + Vec3::Y * head.y * 0.5,
594            ))
595            .material(Material::lit(LISTENER_COLOR)),
596        );
597
598        let right = listener_right(view) * EAR_OFFSET;
599        for (offset, color) in [(right, RIGHT_EAR_COLOR), (-right, LEFT_EAR_COLOR)] {
600            ctx.draw(
601                Sphere { subdivisions: 1 }
602                    .at(Transform::from_scale_rotation_translation(
603                        Vec3::splat(EAR_SIZE),
604                        Quat::IDENTITY,
605                        head + offset,
606                    ))
607                    .material(Material::lit(color)),
608            );
609        }
610
611        ctx.draw(
612            Facing
613                .at(Transform::from_scale_rotation_translation(
614                    Vec3::splat(FACING_MARKER_SIZE),
615                    Quat::IDENTITY,
616                    head + Vec3::NEG_Z * (FACING_MARKER_SIZE * 0.5),
617                ))
618                .material(Material::lit(LISTENER_COLOR)),
619        );
620    }
621
622    fn draw_merge_markers(&self, ctx: &mut FrameContext<'_, SoundCheck>) {
623        if !self.merge_demo {
624            return;
625        }
626        for (position, color) in [(MERGE_POS_A, MERGE_COLOR_A), (MERGE_POS_B, MERGE_COLOR_B)] {
627            ctx.draw(
628                Cube.at(Transform::from_scale_rotation_translation(
629                    Vec3::splat(SOURCE_HALF * 2.0),
630                    Quat::IDENTITY,
631                    position,
632                ))
633                .material(Material::lit(color)),
634            );
635        }
636    }
637
638    /// Draws the ring, each cube as dim as the gain its sustain is declared
639    /// at.
640    fn draw_ring(&self, ctx: &mut FrameContext<'_, SoundCheck>) {
641        if !self.ring_demo {
642            return;
643        }
644        for nth in 0..RING_COUNT {
645            let over = 1.0 - nth as f32 / RING_COUNT as f32;
646            ctx.draw(
647                Cube.at(Transform::from_scale_rotation_translation(
648                    Vec3::splat(SOURCE_HALF),
649                    Quat::IDENTITY,
650                    ring_place(nth),
651                ))
652                .material(Material::lit(RING_COLOR.dimmed(over))),
653            );
654        }
655    }
Source

pub fn map<F>(self, f: F) -> Vec3
where F: FnMut(f32) -> f32,

Returns a vector containing each element of self modified by a mapping function f.

Source

pub fn select(mask: BVec3, if_true: Vec3, if_false: Vec3) -> Vec3

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.

Source

pub const fn from_array(a: [f32; 3]) -> Vec3

Creates a new vector from an array.

Source

pub const fn to_array(&self) -> [f32; 3]

Converts self to [x, y, z]

Source

pub const fn from_slice(slice: &[f32]) -> Vec3

Creates a vector from the first 3 values in slice.

§Panics

Panics if slice is less than 3 elements long.

Source

pub fn write_to_slice(self, slice: &mut [f32])

Writes the elements of self to the first 3 elements in slice.

§Panics

Panics if slice is less than 3 elements long.

Source

pub fn extend(self, w: f32) -> Vec4

Creates a 4D vector from self and the given w value.

Source

pub fn truncate(self) -> Vec2

Creates a 2D vector from the x and y elements of self, discarding z.

Truncation may also be performed by using self.xy().

Source

pub fn from_homogeneous(v: Vec4) -> Vec3

Projects a homogeneous coordinate to 3D space by performing perspective divide.

§Panics

Will panic if v.w is 0 when glam_assert is enabled.

Source

pub fn to_homogeneous(self) -> Vec4

Creates a homogeneous coordinate from self, equivalent to self.extend(1.0).

Source

pub fn to_vec3a(self) -> Vec3A

Source

pub fn with_x(self, x: f32) -> Vec3

Creates a 3D vector from self with the given value of x.

Source

pub fn with_y(self, y: f32) -> Vec3

Creates a 3D vector from self with the given value of y.

Source

pub fn with_z(self, z: f32) -> Vec3

Creates a 3D vector from self with the given value of z.

Source

pub fn dot(self, rhs: Vec3) -> f32

Computes the dot product of self and rhs.

Source

pub fn dot_into_vec(self, rhs: Vec3) -> Vec3

Returns a vector where every component is the dot product of self and rhs.

Source

pub fn cross(self, rhs: Vec3) -> Vec3

Computes the cross product of self and rhs.

Examples found in repository?
examples/stress-preview.rs (line 558)
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}
More examples
Hide additional examples
examples/sound-lab.rs (line 199)
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    }
509
510    fn draw_room(&self, ctx: &mut FrameContext<'_, SoundCheck>) {
511        ctx.draw(
512            Plane
513                .at(Transform::from_scale(Vec3::new(
514                    ROOM_HALF * 2.0,
515                    1.0,
516                    ROOM_HALF * 2.0,
517                )))
518                .material(Material::lit(FLOOR_COLOR)),
519        );
520
521        let side_half = Vec3::new(WALL_THICKNESS * 0.5, WALL_HEIGHT * 0.5, ROOM_HALF);
522        for side in [-1.0, 1.0] {
523            let x = side * (ROOM_HALF - WALL_THICKNESS * 0.5);
524            ctx.draw(
525                Cube.at(Transform::from_scale_rotation_translation(
526                    side_half * 2.0,
527                    Quat::IDENTITY,
528                    Vec3::new(x, side_half.y, 0.0),
529                ))
530                .material(Material::lit(WALL_COLOR)),
531            );
532        }
533        let end_half = Vec3::new(ROOM_HALF, WALL_HEIGHT * 0.5, WALL_THICKNESS * 0.5);
534        for side in [-1.0, 1.0] {
535            let z = side * (ROOM_HALF - WALL_THICKNESS * 0.5);
536            ctx.draw(
537                Cube.at(Transform::from_scale_rotation_translation(
538                    end_half * 2.0,
539                    Quat::IDENTITY,
540                    Vec3::new(0.0, end_half.y, z),
541                ))
542                .material(Material::lit(WALL_COLOR)),
543            );
544        }
545    }
546
547    fn draw_sources(&self, ctx: &mut FrameContext<'_, SoundCheck>) {
548        for (index, source) in self.sources.iter().enumerate() {
549            let color = SOURCE_COLORS[index];
550            let picked_up = self.dragging == Some(index);
551            let scale = if picked_up { 1.3 } else { 1.0 };
552            let emissive = if source.enabled {
553                Color::rgb(color.red * 3.0, color.green * 3.0, color.blue * 3.0)
554            } else {
555                color.dimmed(0.15)
556            };
557
558            for (radius, ring_color) in [
559                (source.range, RANGE_COLOR),
560                (source.reference, REFERENCE_COLOR),
561            ] {
562                ctx.draw(
563                    Ring.at(Transform::from_scale_rotation_translation(
564                        Vec3::new(radius, 1.0, radius),
565                        Quat::IDENTITY,
566                        Vec3::new(source.position.x, 0.01, source.position.z),
567                    ))
568                    .material(Material::color(ring_color)),
569                );
570            }
571            ctx.draw(
572                Cube.at(Transform::from_scale_rotation_translation(
573                    Vec3::splat(SOURCE_HALF * 2.0 * scale),
574                    Quat::IDENTITY,
575                    source.position,
576                ))
577                .material(Material::shaded(color, 0.6).emissive(emissive)),
578            );
579        }
580    }
581
582    /// The listener: a cube drawn from the ground up to [`EYE_HEIGHT`],
583    /// an ear pair set on ± `view`'s right, and a marker at the front that
584    /// shows its fixed `-Z` facing.
585    fn draw_listener(&self, ctx: &mut FrameContext<'_, SoundCheck>, view: View) {
586        let head = view.eye();
587        let ground = Vec3::new(head.x, 0.0, head.z);
588
589        ctx.draw(
590            Cube.at(Transform::from_scale_rotation_translation(
591                Vec3::new(LISTENER_WIDTH, head.y, LISTENER_DEPTH),
592                Quat::IDENTITY,
593                ground + Vec3::Y * head.y * 0.5,
594            ))
595            .material(Material::lit(LISTENER_COLOR)),
596        );
597
598        let right = listener_right(view) * EAR_OFFSET;
599        for (offset, color) in [(right, RIGHT_EAR_COLOR), (-right, LEFT_EAR_COLOR)] {
600            ctx.draw(
601                Sphere { subdivisions: 1 }
602                    .at(Transform::from_scale_rotation_translation(
603                        Vec3::splat(EAR_SIZE),
604                        Quat::IDENTITY,
605                        head + offset,
606                    ))
607                    .material(Material::lit(color)),
608            );
609        }
610
611        ctx.draw(
612            Facing
613                .at(Transform::from_scale_rotation_translation(
614                    Vec3::splat(FACING_MARKER_SIZE),
615                    Quat::IDENTITY,
616                    head + Vec3::NEG_Z * (FACING_MARKER_SIZE * 0.5),
617                ))
618                .material(Material::lit(LISTENER_COLOR)),
619        );
620    }
621
622    fn draw_merge_markers(&self, ctx: &mut FrameContext<'_, SoundCheck>) {
623        if !self.merge_demo {
624            return;
625        }
626        for (position, color) in [(MERGE_POS_A, MERGE_COLOR_A), (MERGE_POS_B, MERGE_COLOR_B)] {
627            ctx.draw(
628                Cube.at(Transform::from_scale_rotation_translation(
629                    Vec3::splat(SOURCE_HALF * 2.0),
630                    Quat::IDENTITY,
631                    position,
632                ))
633                .material(Material::lit(color)),
634            );
635        }
636    }
637
638    /// Draws the ring, each cube as dim as the gain its sustain is declared
639    /// at.
640    fn draw_ring(&self, ctx: &mut FrameContext<'_, SoundCheck>) {
641        if !self.ring_demo {
642            return;
643        }
644        for nth in 0..RING_COUNT {
645            let over = 1.0 - nth as f32 / RING_COUNT as f32;
646            ctx.draw(
647                Cube.at(Transform::from_scale_rotation_translation(
648                    Vec3::splat(SOURCE_HALF),
649                    Quat::IDENTITY,
650                    ring_place(nth),
651                ))
652                .material(Material::lit(RING_COLOR.dimmed(over))),
653            );
654        }
655    }
656
657    /// The controls held at the left: master volume, sustained cues, and
658    /// each source's own knobs.
659    fn side_panel(&mut self, ctx: &mut FrameContext<'_, SoundCheck>) {
660        #[cfg(target_arch = "wasm32")]
661        let unlocked = ctx.sound_unlocked();
662
663        let master_volume = &mut self.master_volume;
664        let theme_on = &mut self.theme_on;
665        let menu_on = &mut self.menu_on;
666        let pulse_on = &mut self.pulse_on;
667        let cue_fade = &mut self.cue_fade;
668        let merge_demo = &mut self.merge_demo;
669        let ring_demo = &mut self.ring_demo;
670        let ring_label = format!("cap demo: sustain {RING_COUNT} sounds at once");
671        let ring_note = format!(
672            "each one is quieter than the one before it, so the engine plays the loudest {MAX_VOICES} and the rest go silent without stopping"
673        );
674        let sources = &mut self.sources;
675
676        ctx.ui(|ui| {
677            egui::Panel::left("controls").show(ui, |ui| {
678                egui::ScrollArea::vertical()
679                    .auto_shrink([false, false])
680                    .show(ui, |ui| {
681                        ui.heading("master");
682                        ui.add(egui::Slider::new(master_volume, 0.0..=1.5).text("volume"));
683                        #[cfg(target_arch = "wasm32")]
684                        if !unlocked {
685                            ui.label("audio unlocks on the first click or key in the browser");
686                        }
687
688                        ui.separator();
689                        ui.heading("cue lab");
690                        ui.label("a checked box is the sustain declaration");
691                        ui.label("unchecking fades it out and parks it");
692                        ui.checkbox(theme_on, Sound::Theme.label());
693                        ui.checkbox(menu_on, Sound::MenuTheme.label());
694                        ui.checkbox(pulse_on, Sound::Pulse.label());
695                        ui.add(egui::Slider::new(cue_fade, 0.0..=3.0).text("fade (seconds)"));
696
697                        ui.separator();
698                        ui.heading("spatial lab");
699                        ui.label("drag a source's marker on the floor to move it");
700                        ui.label(
701                            "a source is at full level inside its gold ring and falls to nothing at the white one",
702                        );
703                        ui.label("red is the right ear (RCA convention), white is the left");
704                        ui.label("the point on the listener always faces -Z");
705                        for (index, source) in sources.iter_mut().enumerate() {
706                            ui.push_id(index, |ui| {
707                                ui.separator();
708                                ui.label(format!("source {}", index + 1));
709                                ui.checkbox(&mut source.enabled, "enabled");
710                                egui::ComboBox::from_label("clip")
711                                    .selected_text(source.sound.label())
712                                    .show_ui(ui, |ui| {
713                                        for choice in Sound::SOURCE_CHOICES {
714                                            ui.selectable_value(
715                                                &mut source.sound,
716                                                choice,
717                                                choice.label(),
718                                            );
719                                        }
720                                    });
721                                ui.add(egui::Slider::new(&mut source.gain, 0.0..=2.0).text("gain"));
722                                ui.add(
723                                    egui::Slider::new(&mut source.range, 1.0..=12.0).text("range"),
724                                );
725                                let range = source.range;
726                                ui.add(
727                                    egui::Slider::new(&mut source.reference, 0.25..=range)
728                                        .text("reference"),
729                                );
730                                ui.add(
731                                    egui::Slider::new(&mut source.pitch, 0.5..=2.0).text("pitch"),
732                                );
733                            });
734                        }
735                        ui.separator();
736                        ui.label(
737                            "each enabled source above sustains at its own instance (0, 1, 2 by position), so the same clip can play at every one without merging into one voice",
738                        );
739                        ui.checkbox(merge_demo, "merge demo: same clip, both at instance 0");
740                        ui.label(
741                            "both declarations below target the same clip at the default instance",
742                        );
743                        ui.label(
744                            "only the one declared last is heard, proof of what the sources above avoid",
745                        );
746                        ui.separator();
747                        ui.checkbox(ring_demo, &ring_label);
748                        ui.label(&ring_note);
749                        ui.label(
750                            "walk into the ring, or turn a source up, and what is played changes with what is loudest",
751                        );
752                    });
753            });
754        });
755    }
756
757    /// Reads what the one-shot controls hold, returning whether `play` and
758    /// `play x32` were pressed this frame — read inside the closure, applied
759    /// after it, since the closure cannot borrow `ctx`.
760    fn one_shot_panel(&mut self, ctx: &mut FrameContext<'_, SoundCheck>) -> (bool, bool) {
761        let mut play_once = false;
762        let mut play_many = false;
763        let durations = &self.durations;
764        let picked = &mut self.picked;
765        let gain = &mut self.one_shot_gain;
766        let pitch = &mut self.one_shot_pitch;
767        let fade = &mut self.one_shot_fade;
768        let trim_start = &mut self.trim_start;
769        let trim_end = &mut self.trim_end;
770        let loop_from = &mut self.one_shot_loop_from;
771        let duration = durations
772            .get(picked)
773            .copied()
774            .unwrap_or_default()
775            .as_secs_f32()
776            .max(0.001);
777
778        ctx.ui(|ui| {
779            egui::Panel::bottom("one-shot").show(ui, |ui| {
780                ui.heading("one-shot lab");
781                egui::ComboBox::from_label("clip")
782                    .selected_text(picked.label())
783                    .show_ui(ui, |ui| {
784                        for choice in Sound::ONE_SHOTS {
785                            if ui
786                                .selectable_label(*picked == choice, choice.label())
787                                .clicked()
788                                && *picked != choice
789                            {
790                                *picked = choice;
791                                *trim_start = 0.0;
792                                *trim_end = durations
793                                    .get(&choice)
794                                    .copied()
795                                    .unwrap_or_default()
796                                    .as_secs_f32();
797                                *loop_from = 0.0;
798                            }
799                        }
800                    });
801
802                ui.add(egui::Slider::new(gain, 0.0..=2.0).text("gain"));
803                ui.add(egui::Slider::new(pitch, 0.5..=2.0).text("pitch"));
804                ui.add(egui::Slider::new(fade, 0.0..=2.0).text("fade (seconds)"));
805
806                duration_bar(ui, duration, trim_start, trim_end, loop_from);
807                ui.label(
808                    "the marker sets loop_from, which a one-shot ignores: only sustain reads it",
809                );
810
811                ui.horizontal(|ui| {
812                    play_once = ui.button("play").clicked();
813                    play_many = ui.button("play ×32 (overruns the voice cap)").clicked();
814                });
815            });
816        });
817
818        (play_once, play_many)
819    }
820
821    fn one_shot_cue(&self) -> SoundCue<Sound> {
822        self.picked
823            .gain(self.one_shot_gain)
824            .pitch(self.one_shot_pitch)
825            .fade(Duration::from_secs_f32(self.one_shot_fade))
826            .trim_to(
827                Duration::from_secs_f32(self.trim_start),
828                Duration::from_secs_f32(self.trim_end),
829            )
830            .loop_from(Duration::from_secs_f32(self.one_shot_loop_from))
831    }
832
833    /// Declares [`RING_COUNT`] sustains on a ring, each less loud than the
834    /// one before it, so the engine's cap plays the loudest of them and the
835    /// rest hold no voice while their playback goes on.
836    fn sustain_ring(&self, ctx: &mut FrameContext<'_, SoundCheck>) {
837        if !self.ring_demo {
838            return;
839        }
840        for nth in 0..RING_COUNT {
841            let gain = RING_GAIN * (1.0 - nth as f32 / RING_COUNT as f32);
842            ctx.sustain(
843                Sound::Pulse
844                    .at(ring_place(nth))
845                    .gain(gain)
846                    .reference(RING_REFERENCE)
847                    .range(RING_RADIUS * 3.0)
848                    .instance(nth + 1),
849            );
850        }
851    }
852
853    fn sustain_cues(&self, ctx: &mut FrameContext<'_, SoundCheck>) {
854        let fade = Duration::from_secs_f32(self.cue_fade);
855        if self.theme_on {
856            ctx.sustain(Sound::Theme.gain(0.5).fade(fade));
857        }
858        if self.menu_on {
859            ctx.sustain(Sound::MenuTheme.gain(0.5).fade(fade));
860        }
861        if self.pulse_on {
862            ctx.sustain(Sound::Pulse.gain(0.3).fade(fade));
863        }
864    }
865}
866
867/// Where the `nth` sustain of the ring stands: around the room, starting
868/// behind the listener's back.
869fn ring_place(nth: u32) -> Vec3 {
870    let turn = TAU * nth as f32 / RING_COUNT as f32;
871
872    Vec3::new(
873        turn.sin() * RING_RADIUS,
874        SOURCE_HEIGHT,
875        turn.cos() * RING_RADIUS,
876    )
877}
878
879/// The direction the ear pair is offset along — the same side the
880/// engine's own pan reads.
881fn listener_right(view: View) -> Vec3 {
882    (view.target() - view.eye())
883        .normalize_or_zero()
884        .cross(view.up())
885}
examples/isometric-board.rs (line 744)
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}
Source

pub fn min(self, rhs: Vec3) -> Vec3

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.

Source

pub fn max(self, rhs: Vec3) -> Vec3

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.

Source

pub fn clamp(self, min: Vec3, max: Vec3) -> Vec3

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.

Source

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.

Source

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.

Source

pub fn min_position(self) -> usize

Returns the index of the first minimum element of self.

Source

pub fn max_position(self) -> usize

Returns the index of the first maximum element of self.

Source

pub fn element_sum(self) -> f32

Returns the sum of all elements of self.

In other words, this computes self.x + self.y + ...

Source

pub fn element_product(self) -> f32

Returns the product of all elements of self.

In other words, this computes self.x * self.y * ...

Source

pub fn cmpeq(self, rhs: Vec3) -> BVec3

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.

Source

pub fn cmpne(self, rhs: Vec3) -> BVec3

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.

Source

pub fn cmpge(self, rhs: Vec3) -> BVec3

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.

Source

pub fn cmpgt(self, rhs: Vec3) -> BVec3

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.

Source

pub fn cmple(self, rhs: Vec3) -> BVec3

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.

Source

pub fn cmplt(self, rhs: Vec3) -> BVec3

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.

Source

pub fn abs(self) -> Vec3

Returns a vector containing the absolute value of each element of self.

Source

pub fn signum(self) -> Vec3

Returns a vector with elements representing the sign of self.

  • 1.0 if the number is positive, +0.0 or INFINITY
  • -1.0 if the number is negative, -0.0 or NEG_INFINITY
  • NAN if the number is NAN
Source

pub fn copysign(self, rhs: Vec3) -> Vec3

Returns a vector with signs of rhs and the magnitudes of self.

Source

pub fn is_negative_bitmask(self) -> u32

Returns a bitmask with the lowest 3 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.

Source

pub fn is_negative_mask(self) -> BVec3

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.

Source

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.

Source

pub fn is_finite_mask(self) -> BVec3

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(), ...].

Source

pub fn is_nan(self) -> bool

Returns true if any elements are NaN.

Source

pub fn is_nan_mask(self) -> BVec3

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(), ...].

Source

pub fn length(self) -> f32

Computes the length of self.

Examples found in repository?
examples/isometric-board.rs (line 309)
304    fn advance(&mut self, dt: Duration) -> bool {
305        let Some(target) = self.target else {
306            return false;
307        };
308        let to_target = target - self.position;
309        let distance = to_target.length();
310        let step = UNIT_SPEED * dt.as_secs_f32();
311        if distance <= step {
312            self.position = target;
313            self.target = None;
314            true
315        } else {
316            self.position += to_target * (step / distance);
317            false
318        }
319    }
More examples
Hide additional examples
examples/animation.rs (line 705)
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    }
Source

pub fn length_squared(self) -> f32

Computes the squared length of self.

This is faster than length() as it avoids a square root operation.

Examples found in repository?
examples/material-playground.rs (line 836)
802    fn fly_camera(&mut self, ctx: &mut FrameContext<'_, Self>) {
803        if !ctx.ui_wants_pointer() && ctx.down(Move::Look) {
804            let look = ctx.axis2(Turn::Look);
805            self.yaw -= look.x;
806            self.pitch = (self.pitch + look.y).clamp(-PITCH_LIMIT, PITCH_LIMIT);
807        }
808
809        let wheel = ctx.axis(Speed::Wheel);
810        if !ctx.ui_wants_pointer() && wheel != 0.0 {
811            self.speed_scale =
812                (self.speed_scale * SPEED_STEP.powf(wheel)).clamp(MIN_SPEED_SCALE, MAX_SPEED_SCALE);
813        }
814
815        let forward = self.forward();
816        let right = Vec3::new(self.yaw.cos(), 0.0, -self.yaw.sin());
817        let mut move_by = Vec3::ZERO;
818        if ctx.down(Move::Forward) {
819            move_by += forward;
820        }
821        if ctx.down(Move::Back) {
822            move_by -= forward;
823        }
824        if ctx.down(Move::Right) {
825            move_by += right;
826        }
827        if ctx.down(Move::Left) {
828            move_by -= right;
829        }
830        if ctx.down(Move::Up) {
831            move_by += Vec3::Y;
832        }
833        if ctx.down(Move::Down) {
834            move_by -= Vec3::Y;
835        }
836        if move_by.length_squared() > 1.0 {
837            move_by = move_by.normalize();
838        }
839
840        self.eye += move_by * MOVE_SPEED * self.speed_scale * ctx.dt().as_secs_f32();
841        self.eye.y = self.eye.y.max(MIN_EYE_HEIGHT);
842    }
Source

pub fn length_recip(self) -> f32

Computes 1.0 / length().

For valid results, self must not be of length zero.

Source

pub fn distance(self, rhs: Vec3) -> f32

Computes the Euclidean distance between two points in space.

Examples found in repository?
examples/animation.rs (line 725)
722    fn patch_underfoot(&self) -> Option<Vec3> {
723        HURT_PATCHES
724            .into_iter()
725            .find(|&patch| self.elf_pos.distance(patch) < HURT_RADIUS)
726    }
727
728    /// Reads the controls and moves the elf, filling [`Self::elf_input`]
729    /// for [`ElfState`] to read.
730    fn tick_elf(&mut self, ctx: &mut TickContext<'_, Scene>) {
731        let grounded = matches!(
732            self.elf_animator.state(),
733            ElfState::Idle | ElfState::Locomotion
734        );
735
736        self.elf_input.speed = self.advance(ctx);
737        self.elf_input.attack = ctx.pressed(Button::Attack);
738        self.elf_input.jump = ctx.pressed(Button::Jump);
739        self.elf_input.dance = ctx.pressed(Button::Dance);
740
741        self.elf_input.near_seat = self.elf_pos.distance(SEAT_POSITION) < SEAT_INTERACT_RADIUS;
742        self.elf_input.interact = ctx.pressed(Button::Interact);
743        if self.elf_input.interact && grounded && self.elf_input.near_seat {
744            self.elf_pos = SEAT_SPOT;
745            self.elf_yaw = SEAT_FACING;
746        }
747
748        let underfoot = self.patch_underfoot();
749        let entered_patch = underfoot.is_some() && !self.in_patch;
750        self.in_patch = underfoot.is_some();
751        self.hits += u32::from(entered_patch);
752        self.elf_input.hit = entered_patch && self.hits < FATAL_HITS;
753        self.elf_input.dying = entered_patch && self.hits >= FATAL_HITS;
754        if entered_patch {
755            self.last_event = match self.elf_input.dying {
756                true => "elf died",
757                false => "elf hit",
758            };
759        }
760    }
761
762    /// Starts a new [`Animator`] over the elf's own state, its position and
763    /// hit count reset with it.
764    fn restart_elf(&mut self) {
765        self.elf_animator = Animator::new();
766        self.elf_pos = ELF_START;
767        self.elf_prev = ELF_START;
768        self.elf_yaw = 0.0;
769        self.elf_height = 0.0;
770        self.elf_height_prev = 0.0;
771        self.jump_speed = 0.0;
772        self.elf_input = ElfInput::default();
773        self.hits = 0;
774        self.in_patch = false;
775        self.last_event = "new elf started";
776    }
777
778    fn panel(&self, ctx: &mut FrameContext<'_, Scene>) {
779        let state = match self.elf_animator.state() {
780            ElfState::Idle => "idle",
781            ElfState::Locomotion if self.elf_input.speed > WALK_CAP => "running",
782            ElfState::Locomotion => "walking",
783            ElfState::Attack => "attacking",
784            ElfState::Hit => "hit",
785            ElfState::Death => "dead",
786            ElfState::SitDown => "sitting down",
787            ElfState::Sit => "sitting",
788            ElfState::StandUp => "standing up",
789            ElfState::Jump => "jumping",
790            ElfState::Dance => "dancing",
791        };
792        ctx.ui(|ui| {
793            egui::Frame::new()
794                .fill(egui::Color32::from_black_alpha(PANEL_BACKDROP))
795                .inner_margin(PANEL_PADDING)
796                .corner_radius(f32::from(PANEL_PADDING))
797                .show(ui, |ui| {
798                    ui.heading(panel_text(format!("elf is {state}")));
799                    ui.label(panel_text(format!(
800                        "hits taken {} of the {} red patches hurt for, {}",
801                        self.hits, FATAL_HITS, self.last_event
802                    )));
803                    ui.label(panel_text(match self.elf_animator.transitioning() {
804                        true => "fading between clips",
805                        false => "one clip playing",
806                    }));
807                    ui.add_space(f32::from(PANEL_PADDING));
808                    egui::Grid::new("controls").show(ui, |ui| {
809                        for (key, does) in CONTROLS {
810                            ui.label(panel_text(key));
811                            ui.label(panel_text(does));
812                            ui.end_row();
813                        }
814                    });
815                });
816        });
817    }
818
819    /// A prompt over the seat, each hurt patch, and the scrubbed elf,
820    /// naming what a player finds there; the seat's own prompt names the
821    /// live binding of `Button::Interact` by its own name, not one fixed
822    /// in the code, and is absent while the elf sits on it.
823    fn draw_prompts(&self, ctx: &mut FrameContext<'_, Scene>, camera: Camera) {
824        let sit_key = ctx
825            .bindings(Button::Interact)
826            .into_iter()
827            .next()
828            .map_or_else(|| "interact".to_owned(), |binding| binding.to_string());
829        let sit = ctx.text_layout(
830            &format!("{sit_key} sits"),
831            egui::FontId::proportional(PROMPT_SIZE),
832        );
833        let hurts = ctx.text_layout("hurts", egui::FontId::proportional(PROMPT_SIZE));
834        let walk_closer = ctx.text_layout("walk closer", egui::FontId::proportional(PROMPT_SIZE));
835
836        let mut prompts = vec![(
837            SCRUBBED_ELF_POSITION + Vec3::Y * (ELF_HEIGHT + PROMPT_LIFT),
838            walk_closer,
839        )];
840        if !self.elf_animator.state().seated() {
841            prompts.push((
842                SEAT_POSITION + Vec3::Y * (SEAT_HEAD_HEIGHT + PROMPT_LIFT),
843                sit,
844            ));
845        }
846        prompts.extend(HURT_PATCHES.map(|patch| (patch + Vec3::Y * PROMPT_LIFT, hurts.clone())));
847
848        let window_size = ctx.window_size();
849        let pixels_per_point = ctx.pixels_per_point();
850        ctx.ui(|ui| {
851            let painter = ui.painter();
852            for (point, galley) in prompts {
853                let Some(pixel) = camera.pixel_of(point, window_size) else {
854                    continue;
855                };
856                let at = logical(pixel, pixels_per_point);
857                let ink = galley.mesh_bounds;
858                let pos = egui::pos2(at.x - ink.center().x, at.y - ink.center().y);
859                let backdrop = egui::Rect::from_center_size(
860                    at,
861                    ink.size() + egui::Vec2::splat(PROMPT_PADDING * 2.0),
862                );
863                painter.rect_filled(
864                    backdrop,
865                    PROMPT_PADDING,
866                    egui::Color32::from_black_alpha(PANEL_BACKDROP),
867                );
868                painter.galley(pos, galley, PANEL_TEXT_COLOR);
869            }
870        });
871    }
872}
873
874impl Game for Scene {
875    type Meshes = Shape;
876    type Sounds = NoSounds;
877    type InputActions = Controls;
878    type Skyboxes = Sky;
879    type SurfaceStyles = NoSurfaceStyles;
880    type PostEffects = NoPostEffects;
881
882    fn tick(&mut self, ctx: &mut TickContext<'_, Scene>) {
883        self.elf_prev = self.elf_pos;
884        self.elf_height_prev = self.elf_height;
885
886        if ctx.pressed(Button::Restart) {
887            self.restart_elf();
888        }
889        if ctx.pressed(Button::Hold) {
890            self.holding = true;
891        }
892        if ctx.pressed(Button::Release) {
893            self.holding = false;
894        }
895
896        self.elf_input.landed = self.fall(ctx.dt().as_secs_f32());
897        self.tick_elf(ctx);
898        ctx.animate(Elf, &mut self.elf_animator, &self.elf_input);
899
900        if self.elf_animator.entered(ElfState::Jump) {
901            self.jump_speed = JUMP_LAUNCH_SPEED;
902        }
903        if self.elf_animator.left(ElfState::StandUp) {
904            self.last_event = "elf stood up";
905        }
906        if self.elf_animator.entered(ElfState::Sit) {
907            self.last_event = "elf sat down";
908        }
909        if self.elf_animator.entered(ElfState::Death) {
910            self.last_event = "elf died";
911        }
912
913        let scrubbed_input = ScrubbedInput {
914            settled: settled_at(self.elf_pos.distance(SCRUBBED_ELF_POSITION)),
915        };
916        ctx.animate(Elf, &mut self.scrubbed_animator, &scrubbed_input);
917        ctx.animate(Butterfly, &mut self.butterfly_animator, &());
918    }
More examples
Hide additional examples
examples/breakout-game.rs (line 696)
689    fn draw_trail(&self, ctx: &mut FrameContext<'_, Breakout>, alpha: f32) {
690        let head = self.ball_trail[1].lerp(self.ball_trail[0], alpha);
691        for i in 0..TRAIL_LEN {
692            let position = self.ball_trail[i + 1].lerp(self.ball_trail[i], alpha);
693            let age = (i + 1) as f32 / TRAIL_LEN as f32;
694            let fade = (1.0 - age).max(TRAIL_ALPHA_FLOOR);
695            let radius = (BALL_RADIUS * TRAIL_SCALE_MIN.lerp(TRAIL_SCALE_MAX, fade))
696                .min((BALL_RADIUS - head.distance(position)).max(0.0));
697            let scale = Vec3::splat(radius * 2.0);
698            ctx.draw(
699                Sphere { subdivisions: 2 }
700                    .at(Transform::from_scale_rotation_translation(
701                        scale,
702                        Quat::IDENTITY,
703                        position,
704                    ))
705                    .material(
706                        Material::color(BALL_GLOW.with_alpha(fade))
707                            .emissive(BALL_EMISSIVE.dimmed(TRAIL_EMISSIVE_PEAK)),
708                    ),
709            );
710        }
711    }
examples/sprite-adventure.rs (line 1170)
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    }
1721
1722    fn frame_overworld(&mut self, ctx: &mut FrameContext<'_, Keep>) {
1723        let drawn_at = self.previous.lerp(self.position, ctx.alpha());
1724        ctx.set_camera(Self::camera(drawn_at, OVERWORLD_CAMERA_OFFSET));
1725        ctx.light(Light::directional(SUN_DIRECTION, SUN_COLOR).shadow());
1726
1727        self.draw_ground(ctx);
1728        self.draw_hedgerow(ctx);
1729        self.draw_pond(ctx);
1730        self.draw_crates(ctx);
1731        self.draw_well(ctx);
1732        self.draw_flora(ctx);
1733        Self::draw_mouth(ctx, ENTRANCE);
1734        self.draw_walker(ctx, drawn_at);
1735    }
1736
1737    fn frame_cave(&mut self, ctx: &mut FrameContext<'_, Keep>) {
1738        let drawn_at = self.previous.lerp(self.position, ctx.alpha());
1739        let camera = Self::camera(drawn_at, CAVE_CAMERA_OFFSET);
1740        ctx.set_camera(camera);
1741
1742        self.draw_cave_floor(ctx);
1743        self.draw_cave_walls(ctx);
1744        Self::draw_door_wall(ctx, (self.ghost > 0.0).then_some(drawn_at.x), self.ghost);
1745        Self::draw_mouth(ctx, EXIT);
1746        self.draw_torches(ctx);
1747        self.draw_door(ctx, self.ghost);
1748        self.draw_door_frame(ctx, self.ghost);
1749        if !self.gem_taken {
1750            self.draw_gem(ctx);
1751        }
1752        self.draw_walker(ctx, drawn_at);
1753        self.draw_door_prompt(ctx, camera);
1754    }
1755
1756    /// Instructions and the door's interact hint — gathered before `ctx.ui`,
1757    /// which cannot read `ctx`.
1758    fn overlay(&mut self, ctx: &mut FrameContext<'_, Keep>) {
1759        let near_door = self.area == Area::Cave
1760            && !self.door_opening
1761            && self.position.distance(INTERACT_POINT) < INTERACT_RADIUS;
1762        let gem_taken = self.area == Area::Cave && self.gem_taken;
1763        let mut reset_clicked = false;
1764
1765        ctx.ui(|ui| {
1766            egui::Frame::new()
1767                .fill(egui::Color32::from_black_alpha(HUD_BACKDROP))
1768                .inner_margin(HUD_PADDING)
1769                .corner_radius(f32::from(HUD_PADDING))
1770                .show(ui, |ui| {
1771                    ui.visuals_mut().override_text_color = Some(egui::Color32::WHITE);
1772                    ui.label("wasd / arrows / stick to walk");
1773                    if near_door {
1774                        ui.label("e / west button to open the door");
1775                    }
1776                    if gem_taken {
1777                        ui.label("gem recovered");
1778                    }
1779                    ui.label("kept between runs: position, gem, cave");
1780                    ui.label("r to reset world");
1781                    if ui.button("reset world").clicked() {
1782                        reset_clicked = true;
1783                    }
1784                });
1785        });
1786
1787        if reset_clicked {
1788            self.reset_requested = true;
1789        }
1790    }
Source

pub fn distance_squared(self, rhs: Vec3) -> f32

Compute the squared euclidean distance between two points in space.

Source

pub fn div_euclid(self, rhs: Vec3) -> Vec3

Returns the element-wise quotient of [Euclidean division] of self by rhs.

Source

pub fn rem_euclid(self, rhs: Vec3) -> Vec3

Returns the element-wise remainder of Euclidean division of self by rhs.

Source

pub fn normalize(self) -> Vec3

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.

Examples found in repository?
examples/breakout-game.rs (line 393)
392    fn launch(&mut self) {
393        self.ball_vel = Vec3::new(0.35, 0.0, -1.0).normalize() * BALL_SPEED;
394        self.phase = Phase::Playing;
395    }
396
397    /// Holds the ball above the paddle while it waits to be served, tracking
398    /// the paddle's own steering, and launches it once the player serves.
399    fn hold_ball(&mut self, ctx: &mut TickContext<'_, Breakout>) {
400        self.ball_prev = self.ball_pos;
401        self.ball_pos.x = self.paddle_x;
402        self.ball_trail = [self.ball_pos; TRAIL_LEN + 1];
403
404        if !ctx.ui_wants_keyboard() && ctx.pressed(Button::Serve) {
405            self.launch();
406            ctx.play(Sound::Serve);
407        }
408    }
409
410    fn step_paddle(&mut self, axis: f32, dt: f32) {
411        self.paddle_prev_x = self.paddle_x;
412        self.paddle_x =
413            (self.paddle_x + axis * PADDLE_SPEED * dt).clamp(-PADDLE_LIMIT, PADDLE_LIMIT);
414    }
415
416    fn step_ball(&mut self, ctx: &mut TickContext<'_, Breakout>, dt: f32) {
417        self.ball_prev = self.ball_pos;
418        self.ball_pos += self.ball_vel * dt;
419
420        self.bounce_walls(ctx);
421        self.bounce_paddle(ctx);
422        self.bounce_bricks(ctx);
423        self.push_trail();
424
425        if self.ball_pos.z - BALL_RADIUS > COURT_HALF_DEPTH {
426            self.lose_life(ctx);
427        }
428    }
429
430    /// Shifts the ghost trail back one slot and records the ball's newly
431    /// resolved position at the front.
432    fn push_trail(&mut self) {
433        self.ball_trail.rotate_right(1);
434        self.ball_trail[0] = self.ball_pos;
435    }
436
437    fn bounce_walls(&mut self, ctx: &mut TickContext<'_, Breakout>) {
438        let left = -COURT_HALF_WIDTH + WALL_THICKNESS;
439        let right = COURT_HALF_WIDTH - WALL_THICKNESS;
440        let top = -COURT_HALF_DEPTH + WALL_THICKNESS;
441
442        let mut hit = false;
443        if self.ball_pos.x - BALL_RADIUS < left {
444            self.ball_pos.x = left + BALL_RADIUS;
445            self.ball_vel.x = self.ball_vel.x.abs();
446            hit = true;
447        } else if self.ball_pos.x + BALL_RADIUS > right {
448            self.ball_pos.x = right - BALL_RADIUS;
449            self.ball_vel.x = -self.ball_vel.x.abs();
450            hit = true;
451        }
452
453        if self.ball_pos.z - BALL_RADIUS < top {
454            self.ball_pos.z = top + BALL_RADIUS;
455            self.ball_vel.z = self.ball_vel.z.abs();
456            hit = true;
457        }
458
459        if hit {
460            ctx.play(Sound::Bounce.pitch(WALL_BOUNCE_PITCH));
461        }
462    }
463
464    /// Bounces the ball off the paddle, steering it by where it landed.
465    fn bounce_paddle(&mut self, ctx: &mut TickContext<'_, Breakout>) {
466        if self.ball_vel.z <= 0.0 {
467            return;
468        }
469        let reach_x = PADDLE_HALF_WIDTH + BALL_RADIUS;
470        let reach_z = PADDLE_HALF_DEPTH + BALL_RADIUS;
471        let dx = self.ball_pos.x - self.paddle_x;
472        let dz = self.ball_pos.z - PADDLE_Z;
473        if dx.abs() > reach_x || dz.abs() > reach_z {
474            return;
475        }
476
477        let offset = (dx / PADDLE_HALF_WIDTH).clamp(-1.0, 1.0);
478        self.ball_vel = Vec3::new(offset, 0.0, -1.0).normalize() * BALL_SPEED;
479        self.ball_pos.z = PADDLE_Z - reach_z;
480        self.paddle_flash = PADDLE_FLASH;
481        ctx.play(Sound::Bounce.pitch(PADDLE_BOUNCE_PITCH));
482    }
483
484    /// Bounces the ball off the nearest overlapping brick, damaging it.
485    fn bounce_bricks(&mut self, ctx: &mut TickContext<'_, Breakout>) {
486        let reach_x = BRICK_HALF_WIDTH + BALL_RADIUS;
487        let reach_z = BRICK_HALF_DEPTH + BALL_RADIUS;
488        let mut broken = None;
489
490        for brick in self
491            .bricks
492            .iter_mut()
493            .filter(|brick| brick.hits_remaining > 0)
494        {
495            let dx = self.ball_pos.x - brick.position.x;
496            let dz = self.ball_pos.z - brick.position.z;
497            if dx.abs() > reach_x || dz.abs() > reach_z {
498                continue;
499            }
500
501            if reach_x - dx.abs() < reach_z - dz.abs() {
502                self.ball_vel.x = if dx < 0.0 {
503                    -self.ball_vel.x.abs()
504                } else {
505                    self.ball_vel.x.abs()
506                };
507            } else {
508                self.ball_vel.z = if dz < 0.0 {
509                    -self.ball_vel.z.abs()
510                } else {
511                    self.ball_vel.z.abs()
512                };
513            }
514
515            brick.hits_remaining -= 1;
516            self.score += 10 * (BRICK_ROWS - brick.row) as u32;
517            ctx.play(Sound::Bounce.pitch(BRICK_BOUNCE_PITCH));
518            if brick.hits_remaining == 0 {
519                ctx.play(
520                    Sound::BrickBreak
521                        .at(brick.position)
522                        .reference(BRICK_BREAK_REFERENCE),
523                );
524                self.brick_flash = BRICK_FLASH;
525                broken = Some((brick.position, BRICK_ROW_COLORS[brick.row]));
526            }
527            break;
528        }
529
530        if let Some((position, color)) = broken {
531            self.spawn_sparks(position, color);
532        }
533
534        if self.bricks.iter().all(|brick| brick.hits_remaining == 0) {
535            self.phase = Phase::Won;
536            ctx.play(Sound::LevelClear);
537        }
538    }
539
540    /// Sends [`SPARK_BURST_COUNT`] sparks outward and upward from a broken
541    /// brick's position, spread by index so no randomness is needed.
542    fn spawn_sparks(&mut self, position: Vec3, color: Color) {
543        for i in 0..SPARK_BURST_COUNT {
544            let t = i as f32 / SPARK_BURST_COUNT as f32;
545            let azimuth = t * TAU;
546            let rise = 0.6 + 0.4 * (t * 3.0).fract();
547            let speed = SPARK_SPEED_MIN.lerp(SPARK_SPEED_MAX, (t * 5.0).fract());
548            let direction = Vec3::new(azimuth.cos(), rise, azimuth.sin()).normalize();
549            self.sparks.push(Spark {
550                position,
551                velocity: direction * speed,
552                roll: azimuth,
553                age: 0.0,
554                color,
555            });
556        }
557    }
More examples
Hide additional examples
examples/flock-parallelism.rs (line 676)
674    fn draw_butterflies(&self, ctx: &mut FrameContext<'_, Self>) {
675        for (position, velocity, kind) in self.butterflies.each() {
676            let rotation = Quat::from_rotation_arc(Vec3::Z, Vec3::from(velocity).normalize());
677            ctx.draw(
678                Butterfly
679                    .at(Transform::from_scale_rotation_translation(
680                        Vec3::splat(BUTTERFLY_SCALE),
681                        rotation,
682                        Vec3::from(position),
683                    ))
684                    .posed(&self.flaps[usize::from(kind.flap)])
685                    .material(Material::lit(TINTS[usize::from(kind.tint)])),
686            );
687        }
688    }
examples/sound-lab.rs (line 199)
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}
examples/material-playground.rs (line 349)
339fn relief_bumps() -> ReliefData {
340    let size = MAP_SIZE;
341    let turns = core::f32::consts::TAU * BUMP_WAVES;
342    let mut pixels = Vec::with_capacity((size.x * size.y * 4) as usize);
343    for y in 0..size.y {
344        for x in 0..size.x {
345            let u = (x as f32 + 0.5) / size.x as f32;
346            let v = (y as f32 + 0.5) / size.y as f32;
347            let slope_u = BUMP_SLOPE * (turns * u).cos() * (turns * v).sin();
348            let slope_v = BUMP_SLOPE * (turns * u).sin() * (turns * v).cos();
349            let normal = Vec3::new(-slope_u, -slope_v, 1.0).normalize();
350            let encode = |signed: f32| ((signed * 0.5 + 0.5) * 255.0).round() as u8;
351            pixels.extend_from_slice(&[encode(normal.x), encode(normal.y), encode(normal.z), 0]);
352        }
353    }
354    ReliefData::normals(size, pixels)
355}
356
357/// `BannerCloth`'s vertices and indices, built twice over: the columns as
358/// authored, facing `+Z`, and the same columns again facing `-Z`, their
359/// triangles in the other order so both draw front side out.
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}
405
406/// Displaced by a wave that grows away from its `x = 0` edge; casts the
407/// shadow of where it was placed, unmoved by its own wave. Its one value
408/// is the clock its wave slides on.
409#[derive(Default, ShaderValues)]
410struct Banner {
411    time: f32,
412}
413
414impl SurfaceStyle for Banner {
415    const PASS: DrawPass = DrawPass::Opaque;
416    const DISPLACE: Option<&'static str> = Some(include_str!("material_playground_banner.wgsl"));
417}
418
419/// A surface that reads no light of the scene's own: it draws its own
420/// pulsing tint, added over what is behind it, through the color it pulses
421/// through and the clock the pulse is timed by.
422#[derive(Default, ShaderValues)]
423struct Field {
424    tint: Color,
425    time: f32,
426}
427
428impl SurfaceStyle for Field {
429    const PASS: DrawPass = DrawPass::Additive;
430    const SURFACE: Option<&'static str> = Some(include_str!("material_playground_field.wgsl"));
431}
432
433surface_styles! { enum Looks { Banner, Field } }
434
435/// A whole scene lighting choice: it names a sky and, kept with it, the
436/// sun that lights the scene, so a choice cannot leave the two apart.
437/// `Dawn`, `Noon`, `Dusk` and `Night` each pair a gradient with a sun of
438/// its own color and direction; `Clear`, `Classic`, `ImageDawn` and
439/// `Sinister` each pair a loaded image with a sun that fits it, and
440/// `LightBlueStars` and `BlueStars` pair a loaded space image with none;
441/// `Default` is the engine's own grey sky and white sun.
442///
443/// [`Skyboxes`] proves every value at startup, so it must be [`Eq`] and
444/// [`Hash`] over a fixed [`Skyboxes::catalog`] — a sky and sun a player
445/// set to any color and direction live could never meet, since `f32` is
446/// neither. This fixed, named set is the shape this file chose in its
447/// place: the side area offers it as one row, and shows the chosen sky's
448/// own light and its sun's own strength as text, read only, rather than
449/// controls a game could not build from. See this example's report for
450/// what that choice costs.
451#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
452enum Sky {
453    Dawn,
454    Noon,
455    Dusk,
456    Night,
457    Clear,
458    Classic,
459    ImageDawn,
460    Sinister,
461    LightBlueStars,
462    BlueStars,
463    Default,
464}
465
466impl Sky {
467    const ALL: [Sky; 11] = [
468        Self::Dawn,
469        Self::Noon,
470        Self::Dusk,
471        Self::Night,
472        Self::Clear,
473        Self::Classic,
474        Self::ImageDawn,
475        Self::Sinister,
476        Self::LightBlueStars,
477        Self::BlueStars,
478        Self::Default,
479    ];
480
481    fn name(self) -> &'static str {
482        match self {
483            Self::Dawn => "dawn",
484            Self::Noon => "noon",
485            Self::Dusk => "dusk",
486            Self::Night => "night",
487            Self::Clear => "clear day",
488            Self::Classic => "classic",
489            Self::ImageDawn => "dawn image",
490            Self::Sinister => "sinister night",
491            Self::LightBlueStars => "light blue stars",
492            Self::BlueStars => "blue stars",
493            Self::Default => "default",
494        }
495    }
496
497    /// The fraction of its own light this sky lands and reflects, through
498    /// [`SkyboxData::lit_by`]: fixed per choice, so a bright one does not
499    /// read too bright, and a dark one does not read too dark, under the
500    /// frame's own lights.
501    fn light(self) -> f32 {
502        match self {
503            Self::Dawn => 0.4,
504            Self::Noon => 0.5,
505            Self::Dusk => 0.35,
506            Self::Night => 0.3,
507            Self::Clear => CLEAR_SKY_LIGHT,
508            Self::Classic => CLASSIC_SKY_LIGHT,
509            Self::ImageDawn => DAWN_SKY_LIGHT,
510            Self::Sinister => SINISTER_SKY_LIGHT,
511            Self::LightBlueStars => LIGHT_BLUE_STARS_LIGHT,
512            Self::BlueStars => BLUE_STARS_LIGHT,
513            Self::Default => 1.0,
514        }
515    }
516
517    /// The sun this choice pairs with its sky: direction, color and
518    /// strength resolved together, so a choice cannot leave them apart.
519    /// `None` for the two space images, which pair with no sun at all.
520    fn sun(self) -> Option<(Vec3, Color, f32)> {
521        match self {
522            Self::Dawn => Some((
523                Vec3::new(-1.0, -0.15, 0.05),
524                Color::rgb(1.0, 0.7, 0.45),
525                1.4,
526            )),
527            Self::Noon => Some((
528                Vec3::new(-0.15, -1.0, -0.1),
529                Color::rgb(1.0, 1.0, 0.98),
530                1.6,
531            )),
532            Self::Dusk => Some((
533                Vec3::new(1.0, -0.15, 0.05),
534                Color::rgb(1.0, 0.55, 0.25),
535                1.2,
536            )),
537            Self::Night => Some((
538                Vec3::new(-0.3, -0.7, -0.6),
539                Color::rgb(0.55, 0.65, 0.85),
540                0.15,
541            )),
542            Self::Clear => Some((
543                Vec3::new(-0.2, -1.0, -0.15),
544                Color::rgb(1.0, 0.98, 0.9),
545                1.5,
546            )),
547            Self::Classic => Some((
548                Vec3::new(-0.4, -0.9, -0.2),
549                Color::rgb(1.0, 0.95, 0.85),
550                1.3,
551            )),
552            Self::ImageDawn => Some((Vec3::new(-1.0, -0.2, 0.1), Color::rgb(1.0, 0.75, 0.5), 1.1)),
553            Self::Sinister => Some((Vec3::new(0.4, -0.5, -0.7), Color::rgb(0.4, 0.5, 0.75), 0.1)),
554            Self::LightBlueStars | Self::BlueStars => None,
555            Self::Default => Some((Vec3::new(-0.4, -1.0, -0.6), Color::WHITE, 1.0)),
556        }
557    }
558
559    /// The color the sky reads under the horizon, through
560    /// [`SkyboxData::with_ground`]: the floor as lit under this choice's own
561    /// sun and [`Self::light`], so it moves with them, not only with the
562    /// image. `None` for the gradient skies and `Default`, which need no
563    /// ground, and for the two space images, which hold space below the
564    /// horizon as well.
565    fn ground(self) -> Option<Color> {
566        match self {
567            Self::Clear => Some(Color::rgb(0.501, 0.517, 0.449)),
568            Self::Classic => Some(Color::rgb(0.420, 0.405, 0.379)),
569            Self::ImageDawn => Some(Color::rgb(0.073, 0.053, 0.032)),
570            Self::Sinister => Some(Color::rgb(0.012, 0.014, 0.020)),
571            Self::Dawn
572            | Self::Noon
573            | Self::Dusk
574            | Self::Night
575            | Self::LightBlueStars
576            | Self::BlueStars
577            | Self::Default => None,
578        }
579    }
580}
581
582impl Catalog for Sky {
583    fn catalog() -> Vec<Self> {
584        Self::ALL.to_vec()
585    }
586}
587
588impl Skyboxes for Sky {
589    fn build(&self, assets: &Assets) -> SkyboxData {
590        let sky = match self {
591            Self::Dawn => SkyboxData::gradient(
592                Color::rgb(0.55, 0.55, 0.75),
593                Color::rgb(0.95, 0.6, 0.35),
594                Color::rgb(0.12, 0.08, 0.06),
595            ),
596            Self::Noon => SkyboxData::gradient(
597                Color::rgb(0.2, 0.45, 0.85),
598                Color::rgb(0.75, 0.82, 0.9),
599                Color::rgb(0.3, 0.3, 0.28),
600            ),
601            Self::Dusk => SkyboxData::gradient(
602                Color::rgb(0.18, 0.1, 0.3),
603                Color::rgb(0.85, 0.35, 0.2),
604                Color::rgb(0.03, 0.02, 0.03),
605            ),
606            Self::Night => SkyboxData::gradient(
607                Color::rgb(0.02, 0.02, 0.06),
608                Color::rgb(0.05, 0.05, 0.1),
609                Color::rgb(0.0, 0.0, 0.0),
610            ),
611            Self::Clear => assets.skybox("sky-clear"),
612            Self::Classic => assets.skybox("sky-classic"),
613            Self::ImageDawn => assets.skybox("sky-dawn"),
614            Self::Sinister => assets.skybox("sky-sinister"),
615            Self::LightBlueStars => assets.skybox("sky-stars-lightblue"),
616            Self::BlueStars => assets.skybox("sky-stars-blue"),
617            Self::Default => SkyboxData::gradient(DEFAULT_SKY, DEFAULT_SKY, DEFAULT_SKY),
618        };
619        let sky = match self.ground() {
620            Some(ground) => sky.with_ground(ground),
621            None => sky,
622        };
623
624        sky.lit_by(self.light())
625    }
626}
627
628/// `color` scaled by `strength`, the value a [`Light`] reads.
629fn scaled(color: Color, strength: f32) -> Color {
630    Color::rgb(
631        color.red * strength,
632        color.green * strength,
633        color.blue * strength,
634    )
635}
636
637/// One light's color and strength, held apart from the position that
638/// names it, plus whether it casts.
639#[derive(Clone, Copy)]
640struct Glow {
641    color: Color,
642    strength: f32,
643    shadow: bool,
644}
645
646impl Glow {
647    /// `color` scaled by `strength`, the value a [`Light`] reads.
648    fn scaled(self) -> Color {
649        scaled(self.color, self.strength)
650    }
651}
652
653/// Every key and button this game reads apart from the UI: held, `Look`
654/// turns the camera by the pointer's own motion, `Forward`/`Back`/
655/// `Left`/`Right` move it along the view and to its side, and `Up`/
656/// `Down` move it along the world's own up.
657#[derive(InputButtonAction, Clone, Copy, PartialEq)]
658enum Move {
659    Forward,
660    Back,
661    Left,
662    Right,
663    Up,
664    Down,
665    Look,
666}
667
668impl InputButtonAction for Move {
669    fn bindings(&self) -> Vec<ButtonBinding> {
670        match self {
671            Self::Forward => vec![Key::W.into()],
672            Self::Back => vec![Key::S.into()],
673            Self::Left => vec![Key::A.into()],
674            Self::Right => vec![Key::D.into()],
675            Self::Up => vec![Key::Space.into()],
676            Self::Down => vec![Key::LeftShift.into()],
677            Self::Look => vec![MouseButton::Right.into()],
678        }
679    }
680}
681
682/// The pointer's own motion, read only while [`Move::Look`] is held.
683#[derive(InputAxis2Action, Clone, Copy, PartialEq)]
684enum Turn {
685    Look,
686}
687
688impl InputAxis2Action for Turn {
689    fn bindings(&self) -> Vec<Axis2Binding> {
690        match self {
691            Self::Look => vec![Axis2Binding::pointer().scale(LOOK_SENSITIVITY)],
692        }
693    }
694}
695
696/// How far the wheel moved this frame, read to scale the move speed.
697#[derive(InputAxisAction, Clone, Copy, PartialEq)]
698enum Speed {
699    Wheel,
700}
701
702impl InputAxisAction for Speed {
703    fn bindings(&self) -> Vec<AxisBinding> {
704        match self {
705            Self::Wheel => vec![AxisBinding::from(WheelDelta::Up).scale(4.0)],
706        }
707    }
708}
709
710struct Controls;
711
712impl InputActions for Controls {
713    type Button = Move;
714    type Axis = Speed;
715    type Axis2 = Turn;
716}
717
718struct Playground {
719    eye: Vec3,
720    yaw: f32,
721    pitch: f32,
722    speed_scale: f32,
723
724    sky: Sky,
725    sun_shadow: bool,
726
727    lamp: Glow,
728    spotlight: Glow,
729
730    front_tint: Color,
731    front_roughness: f32,
732    front_metallic: f32,
733    shading_map_on: bool,
734    relief_map_on: bool,
735    emissive_map_on: bool,
736
737    exposure: f32,
738    bloom: f32,
739}
740
741impl Playground {
742    fn init(ctx: &mut InitContext<'_, Self>) -> Result<Self, Error> {
743        let _ = ctx;
744        Ok(Self {
745            eye: START_EYE,
746            yaw: START_YAW,
747            pitch: START_PITCH,
748            speed_scale: 1.0,
749
750            sky: Sky::Default,
751            sun_shadow: true,
752
753            lamp: Glow {
754                color: Color::rgb(0.9, 0.55, 0.3),
755                strength: 3.0,
756                shadow: false,
757            },
758            spotlight: Glow {
759                color: Color::rgb(0.4, 0.6, 1.0),
760                strength: 6.0,
761                shadow: true,
762            },
763
764            front_tint: Color::rgb(0.7, 0.25, 0.2),
765            front_roughness: 0.4,
766            front_metallic: 0.0,
767            shading_map_on: true,
768            relief_map_on: true,
769            emissive_map_on: true,
770
771            exposure: START_EXPOSURE,
772            bloom: START_BLOOM,
773        })
774    }
775
776    /// This frame's forward direction, from `yaw` (turning around the
777    /// world's own up) and `pitch` (turning up or down).
778    fn forward(&self) -> Vec3 {
779        Vec3::new(
780            -self.pitch.cos() * self.yaw.sin(),
781            self.pitch.sin(),
782            -self.pitch.cos() * self.yaw.cos(),
783        )
784    }
785
786    /// The camera this frame draws from: `eye` looking along `forward`.
787    fn camera(&self) -> Camera {
788        Camera::new(
789            View::look_at(self.eye, self.eye + self.forward()),
790            Projection::perspective(CAMERA_FOV),
791        )
792    }
793
794    /// A held `Move::Look` (the right mouse button) turns the camera by
795    /// the pointer's own motion, the same way it moves: dragging right
796    /// turns the view right and left turns it left, dragging down turns
797    /// it to look further down at the scene, dragging up back toward the
798    /// horizon. `W`/`A`/`S`/`D` move along the view and to its side,
799    /// `Space`/`Left Shift` up and down, and the wheel scales how far
800    /// each move goes. The `eye` is held above the ground plane wherever
801    /// it moves.
802    fn fly_camera(&mut self, ctx: &mut FrameContext<'_, Self>) {
803        if !ctx.ui_wants_pointer() && ctx.down(Move::Look) {
804            let look = ctx.axis2(Turn::Look);
805            self.yaw -= look.x;
806            self.pitch = (self.pitch + look.y).clamp(-PITCH_LIMIT, PITCH_LIMIT);
807        }
808
809        let wheel = ctx.axis(Speed::Wheel);
810        if !ctx.ui_wants_pointer() && wheel != 0.0 {
811            self.speed_scale =
812                (self.speed_scale * SPEED_STEP.powf(wheel)).clamp(MIN_SPEED_SCALE, MAX_SPEED_SCALE);
813        }
814
815        let forward = self.forward();
816        let right = Vec3::new(self.yaw.cos(), 0.0, -self.yaw.sin());
817        let mut move_by = Vec3::ZERO;
818        if ctx.down(Move::Forward) {
819            move_by += forward;
820        }
821        if ctx.down(Move::Back) {
822            move_by -= forward;
823        }
824        if ctx.down(Move::Right) {
825            move_by += right;
826        }
827        if ctx.down(Move::Left) {
828            move_by -= right;
829        }
830        if ctx.down(Move::Up) {
831            move_by += Vec3::Y;
832        }
833        if ctx.down(Move::Down) {
834            move_by -= Vec3::Y;
835        }
836        if move_by.length_squared() > 1.0 {
837            move_by = move_by.normalize();
838        }
839
840        self.eye += move_by * MOVE_SPEED * self.speed_scale * ctx.dt().as_secs_f32();
841        self.eye.y = self.eye.y.max(MIN_EYE_HEIGHT);
842    }
examples/stress-preview.rs (line 376)
359    fn handle_camera(&mut self, ctx: &mut FrameContext<'_, Self>, elapsed: f32) {
360        if ctx.ui_wants_pointer() || ctx.ui_wants_keyboard() {
361            return;
362        }
363        let pan = ctx.axis2(Motion::Pan);
364        let wheel = ctx.axis(Height::Wheel);
365        let look = if ctx.down(Drag::Turn) {
366            ctx.axis2(Motion::Look)
367        } else {
368            Vec2::ZERO
369        };
370        if pan == Vec2::ZERO && wheel == 0.0 && look == Vec2::ZERO {
371            return;
372        }
373
374        let player = self.player.get_or_insert_with(|| {
375            let eye = Self::orbit_eye(elapsed);
376            let forward = (Vec3::ZERO - eye).normalize();
377            Player {
378                eye,
379                yaw: (-forward.x).atan2(-forward.z),
380                pitch: forward.y.asin(),
381            }
382        });
383
384        player.yaw -= look.x;
385        player.pitch = (player.pitch + look.y).clamp(-PITCH_LIMIT, PITCH_LIMIT);
386
387        let forward = Vec3::new(-player.yaw.sin(), 0.0, -player.yaw.cos());
388        let right = Vec3::new(player.yaw.cos(), 0.0, -player.yaw.sin());
389        player.eye += (forward * pan.y + right * pan.x) * PAN_SPEED * ctx.dt().as_secs_f32();
390        player.eye.y =
391            (player.eye.y + wheel * WHEEL_STEP).clamp(MIN_CAMERA_HEIGHT, MAX_CAMERA_HEIGHT);
392    }
393
394    fn draw_ground(ctx: &mut FrameContext<'_, Self>) {
395        let side = (FIELD_RADIUS + FIELD_INNER_RADIUS) * 2.2;
396        ctx.draw(
397            Plane
398                .at(Transform::from_scale(Vec3::new(side, 1.0, side)))
399                .material(Material::lit(GROUND_COLOR).roughness(0.9)),
400        );
401    }
402
403    fn draw_field(&self, ctx: &mut FrameContext<'_, Self>, elapsed: f32) {
404        for entry in &self.field {
405            let yaw = if self.settings.moving && entry.moving {
406                entry.phase + elapsed * MOVING_SPEED
407            } else {
408                entry.phase
409            };
410            ctx.draw(
411                Rock { seed: entry.seed }.at(Transform::from_scale_rotation_translation(
412                    Vec3::ONE,
413                    Quat::from_rotation_y(yaw),
414                    entry.position,
415                )),
416            );
417        }
418    }
419
420    /// How many field items lie in the camera's view at `window_size`:
421    /// every item where the field holds at most [`MAX_IN_VIEW_SAMPLES`],
422    /// otherwise one item stepped at a time and the count scaled back up
423    /// to the whole field; `true` in the second place where the count
424    /// came from such a step.
425    ///
426    /// Each item is tested on the engine's own workers: a parallel iterator
427    /// reaches them with nothing configured for it.
428    fn count_in_view(&self, camera: &Camera, window_size: UVec2) -> (usize, bool) {
429        let stride = (self.field.len() as u32 / MAX_IN_VIEW_SAMPLES).max(1) as usize;
430        let tested = self.field.par_iter().step_by(stride);
431        let tested_count = self.field.len().div_ceil(stride);
432        let in_view = tested
433            .filter(|entry| Self::in_view(camera, entry.position, window_size))
434            .count();
435        let estimate = in_view
436            .checked_mul(self.field.len())
437            .and_then(|scaled| scaled.checked_div(tested_count))
438            .unwrap_or(in_view);
439        (estimate, stride > 1)
440    }
441
442    /// Whether `position` draws inside `window_size`, the frame's own
443    /// bound of what the camera's view holds.
444    fn in_view(camera: &Camera, position: Vec3, window_size: UVec2) -> bool {
445        camera.pixel_of(position, window_size).is_some_and(|pixel| {
446            pixel.x >= 0.0
447                && pixel.y >= 0.0
448                && pixel.x < window_size.x as f32
449                && pixel.y < window_size.y as f32
450        })
451    }
452
453    /// The load controls, and this frame's own cost, reported below them.
454    fn controls(&mut self, ctx: &mut FrameContext<'_, Self>, camera: &Camera) {
455        let submitted = self.field.len();
456        let seeds = self.applied_seed_count;
457        let average_ms = self.frame_times.average_ms();
458        let fps = if average_ms > 0.0 {
459            1000.0 / average_ms
460        } else {
461            0.0
462        };
463        let elapsed = ctx.elapsed().as_secs_f32();
464        let (in_view, sampled) = self.count_in_view(camera, ctx.window_size());
465
466        ctx.ui(|ui| {
467            egui::Frame::new()
468                .fill(egui::Color32::from_gray(24))
469                .inner_margin(PANEL_PADDING)
470                .corner_radius(f32::from(PANEL_PADDING))
471                .show(ui, |ui| {
472                    ui.add(
473                        egui::Slider::new(
474                            &mut self.settings.instance_count,
475                            MIN_INSTANCE_COUNT..=MAX_INSTANCE_COUNT,
476                        )
477                        .text("instance count"),
478                    );
479                    ui.add(
480                        egui::Slider::new(
481                            &mut self.settings.seed_count,
482                            MIN_SEED_COUNT..=MAX_SEED_COUNT,
483                        )
484                        .text("distinct seeds"),
485                    );
486                    ui.checkbox(&mut self.settings.sun_shadow, "sun shadow");
487                    ui.checkbox(&mut self.settings.moving, "moving fraction");
488                    ui.separator();
489                    ui.label(format!("instances submitted {submitted}"));
490                    if sampled {
491                        ui.label(format!("in view, sampled {in_view}"));
492                    } else {
493                        ui.label(format!("instances in view {in_view}"));
494                    }
495                    ui.label(format!("distinct seeds {seeds}"));
496                    ui.label(format!("frame time {average_ms:.2}ms, {fps:.0} fps"));
497                    ui.label(format!("elapsed {elapsed:.1}s"));
498                });
499        });
500    }
501}
502
503/// `instance_count` field values, each drawing one of `seed_count`
504/// distinct seed values in a cycle, and scattered from
505/// [`FIELD_INNER_RADIUS`] out to [`FIELD_RADIUS`]; each built from an
506/// integer-hash of its own index.
507fn build_field(instance_count: u32, seed_count: u32) -> Vec<FieldEntry> {
508    (0..instance_count)
509        .map(|index| {
510            let angle = hash_unit(index, 0) * core::f32::consts::TAU;
511            let spread = hash_unit(index, 1).sqrt();
512            let distance = FIELD_INNER_RADIUS + spread * (FIELD_RADIUS - FIELD_INNER_RADIUS);
513            FieldEntry {
514                seed: index % seed_count,
515                position: Vec3::new(angle.cos() * distance, 0.0, angle.sin() * distance),
516                phase: hash_unit(index, 2) * core::f32::consts::TAU,
517                moving: index % MOVING_STRIDE == 0,
518            }
519        })
520        .collect()
521}
522
523/// A rock built from `seed`: a cone of [`ROCK_SIDES`] sides, each base
524/// corner and the apex height displaced by an integer-hash of `seed`.
525fn build_rock(seed: u32) -> MeshData {
526    let height = ROCK_HEIGHT * (1.0 + hash_signed(seed, ROCK_SIDES) * ROCK_HEIGHT_DISPLACEMENT);
527    let apex = Vec3::Y * height;
528    let base: Vec<Vec3> = (0..ROCK_SIDES)
529        .map(|corner| {
530            let angle = core::f32::consts::TAU * corner as f32 / ROCK_SIDES as f32;
531            let radius =
532                ROCK_BASE_RADIUS * (1.0 + hash_signed(seed, corner) * ROCK_RADIAL_DISPLACEMENT);
533            Vec3::new(angle.cos() * radius, 0.0, angle.sin() * radius)
534        })
535        .collect();
536
537    let mut vertices = Vec::with_capacity(base.len() * 6);
538    let mut indices = Vec::with_capacity(base.len() * 6);
539    for corner in 0..base.len() {
540        let next = (corner + 1) % base.len();
541        push_face(&mut vertices, &mut indices, base[corner], apex, base[next]);
542        push_face(
543            &mut vertices,
544            &mut indices,
545            base[corner],
546            base[next],
547            Vec3::ZERO,
548        );
549    }
550
551    MeshData::new(vertices, indices).with_material(Material::lit(ROCK_COLOR))
552}
553
554/// One triangle, shaded flat, over `a`, `b`, `c`, in the order that faces
555/// outward: counter-clockwise as seen from the side its own normal points
556/// to.
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}
examples/isometric-board.rs (line 744)
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}
Source

pub fn try_normalize(self) -> Option<Vec3>

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().

Examples found in repository?
examples/animation.rs (line 697)
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    }
Source

pub fn normalize_or(self, fallback: Vec3) -> Vec3

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().

Source

pub fn normalize_or_zero(self) -> Vec3

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().

Examples found in repository?
examples/sound-lab.rs (line 883)
881fn listener_right(view: View) -> Vec3 {
882    (view.target() - view.eye())
883        .normalize_or_zero()
884        .cross(view.up())
885}
Source

pub fn normalize_and_length(self) -> (Vec3, 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.

Source

pub fn is_normalized(self) -> bool

Returns whether self is length 1.0 or not.

Uses a precision threshold of approximately 1e-4.

Source

pub fn project_onto(self, rhs: Vec3) -> Vec3

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.

Source

pub fn reject_from(self, rhs: Vec3) -> Vec3

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.

Source

pub fn project_onto_normalized(self, rhs: Vec3) -> Vec3

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.

Source

pub fn reject_from_normalized(self, rhs: Vec3) -> Vec3

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.

Source

pub fn round(self) -> Vec3

Returns a vector containing the nearest integer to a number for each element of self. Round half-way cases away from 0.0.

Source

pub fn floor(self) -> Vec3

Returns a vector containing the largest integer less than or equal to a number for each element of self.

Source

pub fn ceil(self) -> Vec3

Returns a vector containing the smallest integer greater than or equal to a number for each element of self.

Source

pub fn trunc(self) -> Vec3

Returns a vector containing the integer part each element of self. This means numbers are always truncated towards zero.

Source

pub fn step(self, rhs: Vec3) -> Vec3

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)

Source

pub fn saturate(self) -> Vec3

Returns a vector containing all elements of self clamped to the range of [0, 1].

Source

pub fn fract(self) -> Vec3

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.

Source

pub fn fract_gl(self) -> Vec3

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.

Source

pub fn exp(self) -> Vec3

Returns a vector containing e^self (the exponential function) for each element of self.

Source

pub fn exp2(self) -> Vec3

Returns a vector containing 2^self for each element of self.

Source

pub fn ln(self) -> Vec3

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.

Source

pub fn log2(self) -> Vec3

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.

Source

pub fn powf(self, n: f32) -> Vec3

Returns a vector containing each element of self raised to the power of n.

Source

pub fn sqrt(self) -> Vec3

Returns a vector containing the square root for each element of self. This returns NaN when the element is negative.

Source

pub fn cos(self) -> Vec3

Returns a vector containing the cosine for each element of self.

Source

pub fn sin(self) -> Vec3

Returns a vector containing the sine for each element of self.

Source

pub fn sin_cos(self) -> (Vec3, Vec3)

Returns a tuple of two vectors containing the sine and cosine for each element of self.

Source

pub fn recip(self) -> Vec3

Returns a vector containing the reciprocal 1.0/n of each element of self.

Source

pub fn lerp(self, rhs: Vec3, s: f32) -> Vec3

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?
examples/sprite-adventure.rs (line 1723)
1722    fn frame_overworld(&mut self, ctx: &mut FrameContext<'_, Keep>) {
1723        let drawn_at = self.previous.lerp(self.position, ctx.alpha());
1724        ctx.set_camera(Self::camera(drawn_at, OVERWORLD_CAMERA_OFFSET));
1725        ctx.light(Light::directional(SUN_DIRECTION, SUN_COLOR).shadow());
1726
1727        self.draw_ground(ctx);
1728        self.draw_hedgerow(ctx);
1729        self.draw_pond(ctx);
1730        self.draw_crates(ctx);
1731        self.draw_well(ctx);
1732        self.draw_flora(ctx);
1733        Self::draw_mouth(ctx, ENTRANCE);
1734        self.draw_walker(ctx, drawn_at);
1735    }
1736
1737    fn frame_cave(&mut self, ctx: &mut FrameContext<'_, Keep>) {
1738        let drawn_at = self.previous.lerp(self.position, ctx.alpha());
1739        let camera = Self::camera(drawn_at, CAVE_CAMERA_OFFSET);
1740        ctx.set_camera(camera);
1741
1742        self.draw_cave_floor(ctx);
1743        self.draw_cave_walls(ctx);
1744        Self::draw_door_wall(ctx, (self.ghost > 0.0).then_some(drawn_at.x), self.ghost);
1745        Self::draw_mouth(ctx, EXIT);
1746        self.draw_torches(ctx);
1747        self.draw_door(ctx, self.ghost);
1748        self.draw_door_frame(ctx, self.ghost);
1749        if !self.gem_taken {
1750            self.draw_gem(ctx);
1751        }
1752        self.draw_walker(ctx, drawn_at);
1753        self.draw_door_prompt(ctx, camera);
1754    }
More examples
Hide additional examples
examples/isometric-board.rs (line 584)
583    fn draw_sprite(&self, ctx: &mut FrameContext<'_, Board>, hover: Hover) {
584        let position = self.sprite.previous.lerp(self.sprite.position, ctx.alpha());
585        let current = self.turn == Turn::Sprite;
586        let (tint, glow) = if current && self.selected {
587            (SELECTED_TINT, SELECTED_GLOW)
588        } else if current && hover == Hover::CurrentUnit {
589            (HOVER_TINT, HOVER_GLOW)
590        } else if current {
591            (TURN_TINT, TURN_GLOW)
592        } else {
593            (Color::WHITE, Color::BLACK)
594        };
595        ctx.draw(
596            Sprite
597                .at(Transform::from_scale_rotation_translation(
598                    Vec3::new(SPRITE_WIDTH, SPRITE_HEIGHT, 1.0),
599                    Quat::IDENTITY,
600                    position,
601                ))
602                .upright()
603                .frame(sprite_frame(self.sprite.facing_right))
604                .material(Material::lit(tint).cutout().emissive(glow)),
605        );
606    }
607
608    fn draw_block(&self, ctx: &mut FrameContext<'_, Board>, hover: Hover) {
609        let position = self.block.previous.lerp(self.block.position, ctx.alpha());
610        let current = self.turn == Turn::Block;
611        let (color, glow) = if current && self.selected {
612            (SELECTED_TINT, SELECTED_GLOW)
613        } else if current && hover == Hover::CurrentUnit {
614            (HOVER_TINT, HOVER_GLOW)
615        } else if current {
616            (BLOCK_TURN, TURN_GLOW)
617        } else {
618            (BLOCK_IDLE, Color::BLACK)
619        };
620        ctx.draw(
621            Cube.at(Transform::from_scale_rotation_translation(
622                Vec3::splat(BLOCK_SIZE),
623                Quat::IDENTITY,
624                position,
625            ))
626            .material(Material::lit(color).emissive(glow)),
627        );
628    }
examples/breakout-game.rs (line 690)
689    fn draw_trail(&self, ctx: &mut FrameContext<'_, Breakout>, alpha: f32) {
690        let head = self.ball_trail[1].lerp(self.ball_trail[0], alpha);
691        for i in 0..TRAIL_LEN {
692            let position = self.ball_trail[i + 1].lerp(self.ball_trail[i], alpha);
693            let age = (i + 1) as f32 / TRAIL_LEN as f32;
694            let fade = (1.0 - age).max(TRAIL_ALPHA_FLOOR);
695            let radius = (BALL_RADIUS * TRAIL_SCALE_MIN.lerp(TRAIL_SCALE_MAX, fade))
696                .min((BALL_RADIUS - head.distance(position)).max(0.0));
697            let scale = Vec3::splat(radius * 2.0);
698            ctx.draw(
699                Sphere { subdivisions: 2 }
700                    .at(Transform::from_scale_rotation_translation(
701                        scale,
702                        Quat::IDENTITY,
703                        position,
704                    ))
705                    .material(
706                        Material::color(BALL_GLOW.with_alpha(fade))
707                            .emissive(BALL_EMISSIVE.dimmed(TRAIL_EMISSIVE_PEAK)),
708                    ),
709            );
710        }
711    }
712
713    /// Draws one held ball for every life past the one in play, set in a
714    /// row alongside the paddle's own path.
715    fn draw_lives(&self, ctx: &mut FrameContext<'_, Breakout>) {
716        let held_lives = self.lives.saturating_sub(1);
717        for slot in 0..held_lives {
718            let z = PADDLE_Z + (slot + 1) as f32 * LIFE_ROW_SPACING;
719            ctx.draw(
720                Sphere { subdivisions: 2 }
721                    .at(Transform::from_scale_rotation_translation(
722                        Vec3::splat(BALL_RADIUS * 2.0),
723                        Quat::IDENTITY,
724                        Vec3::new(LIFE_ROW_X, BALL_RADIUS, z),
725                    ))
726                    .material(
727                        Material::color(BALL_GLOW)
728                            .emissive(BALL_EMISSIVE)
729                            .additive(),
730                    ),
731            );
732        }
733    }
734
735    fn overlay(&mut self, ctx: &mut FrameContext<'_, Breakout>) {
736        let bricks_left = self
737            .bricks
738            .iter()
739            .filter(|brick| brick.hits_remaining > 0)
740            .count();
741        // Read before `ctx.ui` so a rebind changes what the hint reads this
742        // frame too.
743        let move_hint = bindings_text(ctx.bindings(Move::Paddle));
744        let pause_hint = bindings_text(ctx.bindings(Button::Pause));
745        let serve_hint = bindings_text(ctx.bindings(Button::Serve));
746        ctx.ui(|ui| {
747            ui.horizontal(|ui| {
748                ui.label(egui::RichText::new(format!("score {}", self.score)).size(32.0));
749                ui.label(format!("{bricks_left} bricks left"));
750            });
751            ui.label(format!("move: {move_hint} · {pause_hint} to pause"));
752            if self.phase == Phase::Serving {
753                ui.label(format!("{serve_hint} to serve"));
754            }
755        });
756
757        match self.phase {
758            Phase::Serving | Phase::Playing if self.paused => self.menu(ctx, "paused", false),
759            Phase::Won => self.menu(ctx, "you win", true),
760            Phase::Lost => self.menu(ctx, "game over", true),
761            _ => {}
762        }
763    }
764
765    fn menu(&mut self, ctx: &mut FrameContext<'_, Breakout>, title: &str, over: bool) {
766        let mut clicked = false;
767        let mut quit = false;
768
769        // `ctx.ui` cannot borrow `ctx`, so anything the controls list needs is
770        // read first and applied after.
771        let buttons: Vec<(Button, String)> = Button::all()
772            .into_iter()
773            .map(|action| (action, bindings_text(ctx.bindings(action))))
774            .collect();
775        let axes: Vec<(Move, String)> = Move::all()
776            .into_iter()
777            .map(|action| (action, bindings_text(ctx.bindings(action))))
778            .collect();
779        let listening = self.listening;
780        let actuated_button = (!ctx.ui_wants_keyboard())
781            .then(|| ctx.actuated_button())
782            .flatten();
783        let actuated_axis = (!ctx.ui_wants_keyboard())
784            .then(|| ctx.actuated_axis())
785            .flatten();
786        let mut reset = None;
787
788        ctx.ui(|ui| {
789            egui::Window::new(title)
790                .collapsible(false)
791                .resizable(false)
792                .anchor(egui::Align2::CENTER_CENTER, egui::Vec2::ZERO)
793                .show(ui.ctx(), |ui| {
794                    if over {
795                        ui.label(format!("score {}", self.score));
796                    }
797                    if !over {
798                        ui.add(
799                            egui::Slider::new(&mut self.master_volume, 0.0..=1.0).text("volume"),
800                        );
801                        if ui.button("resume").clicked() {
802                            self.paused = false;
803                            clicked = true;
804                        }
805                        ui.separator();
806                        ui.heading("controls");
807                        for (action, text) in &buttons {
808                            controls_row(
809                                ui,
810                                action.name(),
811                                text,
812                                listening == Some(Listening::Button(*action)),
813                                &mut self.listening,
814                                Listening::Button(*action),
815                                &mut reset,
816                            );
817                        }
818                        for (action, text) in &axes {
819                            controls_row(
820                                ui,
821                                action.name(),
822                                text,
823                                listening == Some(Listening::Move(*action)),
824                                &mut self.listening,
825                                Listening::Move(*action),
826                                &mut reset,
827                            );
828                        }
829                    }
830                    if ui.button("restart").clicked() {
831                        self.restart();
832                        clicked = true;
833                    }
834                    if ui.button("quit").clicked() {
835                        quit = true;
836                    }
837                });
838        });
839
840        match (self.listening, actuated_button, actuated_axis) {
841            (Some(Listening::Button(action)), Some(binding), _) => {
842                ctx.rebind(action, vec![binding]);
843                self.listening = None;
844            }
845            (Some(Listening::Move(action)), _, Some(binding)) => {
846                ctx.rebind(action, vec![binding]);
847                self.listening = None;
848            }
849            _ => {}
850        }
851        match reset {
852            Some(Listening::Button(action)) => ctx.rebind(action, action.bindings()),
853            Some(Listening::Move(action)) => ctx.rebind(action, action.bindings()),
854            None => {}
855        }
856
857        if clicked {
858            ctx.play(Sound::Click);
859        }
860        if quit {
861            ctx.close();
862        }
863    }
864
865    /// Sustains both tracks every frame, and the gain goes to whichever the
866    /// game calls for: gameplay music while a round is live, serving
867    /// included, and menu music whenever a menu covers it.
868    ///
869    /// Each fades in over [`MUSIC_CROSSFADE`] and slides every later gain
870    /// over it, which is the crossfade itself; the one at no gain costs no
871    /// voice while its playback goes on under the other.
872    fn sustain_music(&self, ctx: &mut FrameContext<'_, Breakout>) {
873        let playing = !self.paused && matches!(self.phase, Phase::Serving | Phase::Playing);
874        let gain = |wanted: bool| match wanted {
875            true => MUSIC_GAIN,
876            false => 0.0,
877        };
878
879        ctx.sustain(
880            Sound::Music
881                .gain(gain(playing))
882                .fade(MUSIC_CROSSFADE)
883                .glide(MUSIC_CROSSFADE)
884                .loop_from(MUSIC_LOOP_FROM),
885        );
886        ctx.sustain(
887            Sound::MenuMusic
888                .gain(gain(!playing))
889                .fade(MUSIC_CROSSFADE)
890                .glide(MUSIC_CROSSFADE)
891                .loop_from(MENU_MUSIC_LOOP_FROM),
892        );
893    }
894}
895
896/// One action's name, its live bindings, a rebind control that starts
897/// listening for a new one, and a reset to its defaults; cancel is a
898/// button rather than Escape, since Escape is itself a binding a listen
899/// could capture.
900fn controls_row(
901    ui: &mut egui::Ui,
902    name: &str,
903    bindings: &str,
904    listening: bool,
905    target: &mut Option<Listening>,
906    action: Listening,
907    reset: &mut Option<Listening>,
908) {
909    ui.horizontal(|ui| {
910        ui.label(format!("{name}: {bindings}"));
911        if listening {
912            ui.label("listening");
913            if ui.button("cancel").clicked() {
914                *target = None;
915            }
916        } else if ui.button("rebind").clicked() {
917            *target = Some(action);
918        }
919        if ui.button("reset").clicked() {
920            *reset = Some(action);
921        }
922    });
923}
924
925/// The controls-menu text for a live binding list: each alternative,
926/// separated, in the order the player can use them.
927fn bindings_text<B: Display>(bindings: Vec<B>) -> String {
928    bindings
929        .iter()
930        .map(ToString::to_string)
931        .collect::<Vec<_>>()
932        .join(", ")
933}
934
935fn spawn_bricks() -> Vec<Brick> {
936    let cell = BRICK_HALF_WIDTH * 2.0 + BRICK_GAP;
937    let row_span = BRICK_HALF_DEPTH * 2.0 + BRICK_ROW_GAP;
938    let grid_width = cell * BRICK_COLUMNS as f32 - BRICK_GAP;
939    let start_x = -grid_width * 0.5 + BRICK_HALF_WIDTH;
940    let start_z = -COURT_HALF_DEPTH + WALL_THICKNESS + BRICK_HALF_DEPTH + 0.6;
941
942    (0..BRICK_ROWS)
943        .flat_map(|row| {
944            (0..BRICK_COLUMNS).map(move |column| Brick {
945                row,
946                position: Vec3::new(
947                    start_x + column as f32 * cell,
948                    BRICK_HALF_HEIGHT,
949                    start_z + row as f32 * row_span,
950                ),
951                hits_remaining: BRICK_HITS,
952            })
953        })
954        .collect()
955}
956
957impl Game for Breakout {
958    type Meshes = Shape;
959    type Sounds = Sound;
960    type InputActions = Controls;
961    type Skyboxes = NoSkyboxes;
962    type SurfaceStyles = NoSurfaceStyles;
963    type PostEffects = NoPostEffects;
964
965    fn tick(&mut self, ctx: &mut TickContext<'_, Breakout>) {
966        if self.paused {
967            return;
968        }
969
970        let dt = ctx.dt().as_secs_f32();
971        self.paddle_flash = (self.paddle_flash - dt).max(0.0);
972        self.brick_flash = (self.brick_flash - dt).max(0.0);
973        self.life_lost_flash = (self.life_lost_flash - dt).max(0.0);
974        self.step_sparks(dt);
975
976        // Decay runs before the end-screen return below, so the last pulse and
977        // burst do not stay on screen.
978        if matches!(self.phase, Phase::Won | Phase::Lost) {
979            return;
980        }
981
982        let axis = if ctx.ui_wants_keyboard() {
983            0.0
984        } else {
985            ctx.axis(Move::Paddle)
986        };
987        self.step_paddle(axis, dt);
988
989        match self.phase {
990            Phase::Serving => self.hold_ball(ctx),
991            _ => self.step_ball(ctx, dt),
992        }
993    }
994
995    fn frame(&mut self, ctx: &mut FrameContext<'_, Breakout>) {
996        if matches!(self.phase, Phase::Serving | Phase::Playing) && ctx.pressed(Button::Pause) {
997            self.paused = !self.paused;
998        }
999
1000        ctx.set_volume(self.master_volume);
1001        self.sustain_music(ctx);
1002
1003        ctx.set_camera(Self::camera());
1004
1005        let brick_pulse = (self.brick_flash / BRICK_FLASH).clamp(0.0, 1.0);
1006        ctx.set_bloom((BLOOM_BASE + brick_pulse * BLOOM_PULSE_PEAK).clamp(0.0, 1.0));
1007
1008        let life_lost_t = (self.life_lost_flash / LIFE_LOST_FLASH).clamp(0.0, 1.0);
1009        ctx.set_exposure((1.0 - life_lost_t * EXPOSURE_DIP_DEPTH).clamp(0.0, 1.0));
1010
1011        // The tick moves nothing behind a menu, so a frame there draws the last
1012        // step whole rather than interpolating from the one before.
1013        let alpha = match self.phase {
1014            Phase::Serving | Phase::Playing if !self.paused => ctx.alpha(),
1015            _ => 1.0,
1016        };
1017        let paddle_x = self.paddle_prev_x.lerp(self.paddle_x, alpha);
1018        let ball_pos = self.ball_prev.lerp(self.ball_pos, alpha);
1019
1020        ctx.light(Light::point(ball_pos, BALL_GLOW, BALL_LIGHT_RANGE).shadow());
1021
1022        self.draw_court(ctx);
1023        self.draw_bricks(ctx);
1024        self.draw_sparks(ctx);
1025        self.draw_lives(ctx);
1026
1027        ctx.draw(
1028            Paddle
1029                .at(Transform::from_translation(Vec3::new(
1030                    paddle_x,
1031                    PADDLE_HALF_HEIGHT,
1032                    PADDLE_Z,
1033                )))
1034                .material_of(PaddlePart::Face, self.paddle_face_material()),
1035        );
1036
1037        self.draw_trail(ctx, alpha);
1038        ctx.draw(
1039            Sphere { subdivisions: 2 }
1040                .at(Transform::from_scale_rotation_translation(
1041                    Vec3::splat(BALL_RADIUS * 2.0),
1042                    Quat::IDENTITY,
1043                    ball_pos,
1044                ))
1045                .material(
1046                    Material::color(BALL_GLOW)
1047                        .emissive(BALL_EMISSIVE)
1048                        .additive(),
1049                ),
1050        );
1051
1052        self.overlay(ctx);
1053    }
examples/animation.rs (line 924)
920    fn frame(&mut self, ctx: &mut FrameContext<'_, Scene>) {
921        self.steer_camera(ctx);
922
923        let alpha = ctx.alpha();
924        let elf_pos = self.elf_prev.lerp(self.elf_pos, alpha);
925        let elf_height = self.elf_height_prev + (self.elf_height - self.elf_height_prev) * alpha;
926        let (butterfly_pos, butterfly_yaw) = butterfly_pose(ctx.elapsed().as_secs_f32());
927
928        let camera = orbit_camera(elf_pos, self.camera_yaw, self.camera_pitch);
929        ctx.set_camera(camera);
930        ctx.set_cursor(if self.holding {
931            Cursor::Held
932        } else {
933            Cursor::Arrow
934        });
935        ctx.set_skybox(Sky::Day);
936        ctx.set_exposure(3.0);
937        ctx.set_bloom(0.2);
938        ctx.light(Light::directional(SUN_DIRECTION, SUN_COLOR).shadow());
939        ctx.light(
940            Light::point(
941                LAMP_POST_POSITION + Vec3::Y * (LAMP_POST_HEIGHT + LAMP_HEAD_GAP * 0.5),
942                LAMP_LIGHT_COLOR,
943                LAMP_LIGHT_RANGE,
944            )
945            .shadow(),
946        );
947        ctx.light(
948            Light::spot(Spot {
949                position: SPOT_POSITION,
950                direction: SPOT_DIRECTION,
951                color: SPOT_COLOR,
952                range: SPOT_RANGE,
953                angle: SPOT_ANGLE,
954            })
955            .shadow(),
956        );
957        ctx.light(
958            Light::point(butterfly_pos, BUTTERFLY_LIGHT_COLOR, BUTTERFLY_LIGHT_RANGE).shadow(),
959        );
960
961        ctx.draw(
962            Plane
963                .at(Transform::from_scale(Vec3::new(
964                    GROUND_SIZE,
965                    1.0,
966                    GROUND_SIZE,
967                )))
968                .material(Material::lit(GROUND_COLOR)),
969        );
970        for patch in HURT_PATCHES {
971            ctx.draw(
972                Plane
973                    .at(Transform::from_scale_rotation_translation(
974                        Vec3::splat(HURT_RADIUS * 2.0),
975                        Quat::IDENTITY,
976                        patch,
977                    ))
978                    .material(Material::lit(HURT_COLOR)),
979            );
980        }
981        ctx.draw(
982            Cube.at(Transform::from_scale_rotation_translation(
983                Vec3::new(SEAT_FOOTPRINT, SEAT_HEIGHT, SEAT_FOOTPRINT),
984                Quat::IDENTITY,
985                SEAT_POSITION + Vec3::Y * SEAT_HEIGHT * 0.5,
986            ))
987            .material(Material::lit(SEAT_COLOR)),
988        );
989        ctx.draw(
990            Cube.at(Transform::from_scale_rotation_translation(
991                Vec3::new(LAMP_POST_THICKNESS, LAMP_POST_HEIGHT, LAMP_POST_THICKNESS),
992                Quat::IDENTITY,
993                LAMP_POST_POSITION + Vec3::Y * LAMP_POST_HEIGHT * 0.5,
994            ))
995            .material(Material::lit(LAMP_POST_COLOR)),
996        );
997        ctx.draw(
998            Cube.at(Transform::from_scale_rotation_translation(
999                Vec3::splat(LAMP_HEAD_SIZE),
1000                Quat::IDENTITY,
1001                LAMP_POST_POSITION
1002                    + Vec3::Y * (LAMP_POST_HEIGHT + LAMP_HEAD_GAP + LAMP_HEAD_SIZE * 0.5),
1003            ))
1004            .material(Material::color(Color::BLACK).emissive(LAMP_LIGHT_COLOR)),
1005        );
1006        ctx.draw(
1007            Cube.at(Transform::from_scale_rotation_translation(
1008                Vec3::splat(SPOT_FIXTURE_SIZE),
1009                Quat::IDENTITY,
1010                SPOT_POSITION + Vec3::Y * SPOT_FIXTURE_SIZE * 0.5,
1011            ))
1012            .material(Material::lit(SPOT_FIXTURE_COLOR)),
1013        );
1014
1015        ctx.draw(
1016            Elf.at(Transform::from_rotation_translation(
1017                Quat::from_rotation_y(self.elf_yaw),
1018                elf_pos + Vec3::Y * elf_height,
1019            ))
1020            .posed(&self.elf_animator),
1021        );
1022        ctx.draw(
1023            Elf.at(Transform::from_rotation_translation(
1024                Quat::from_rotation_y(core::f32::consts::PI),
1025                SCRUBBED_ELF_POSITION,
1026            ))
1027            .posed(&self.scrubbed_animator),
1028        );
1029        ctx.draw(
1030            Butterfly
1031                .at(Transform::from_rotation_translation(
1032                    Quat::from_rotation_y(butterfly_yaw),
1033                    butterfly_pos,
1034                ))
1035                .posed(&self.butterfly_animator)
1036                .material(Material::lit(Color::WHITE).emissive(BUTTERFLY_EMISSIVE)),
1037        );
1038
1039        self.draw_prompts(ctx, camera);
1040        self.panel(ctx);
1041    }
Source

pub fn move_towards(self, rhs: Vec3, d: f32) -> Vec3

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.

Source

pub fn midpoint(self, rhs: Vec3) -> Vec3

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.

Source

pub fn abs_diff_eq(self, rhs: Vec3, 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.

Source

pub fn clamp_length(self, min: f32, max: f32) -> Vec3

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.

Source

pub fn clamp_length_max(self, max: f32) -> Vec3

Returns a vector with a length no more than max.

§Panics

Will panic if max is negative when glam_assert is enabled.

Source

pub fn clamp_length_min(self, min: f32) -> Vec3

Returns a vector with a length no less than min.

§Panics

Will panic if min is negative when glam_assert is enabled.

Source

pub fn mul_add(self, a: Vec3, b: Vec3) -> Vec3

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.

Source

pub fn reflect(self, normal: Vec3) -> Vec3

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.

Source

pub fn refract(self, normal: Vec3, eta: f32) -> Vec3

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.

Source

pub fn angle_between(self, rhs: Vec3) -> f32

Returns the angle (in radians) between two vectors in the range [0, +π].

For the full rotation between two vectors as a quaternion, see Quat::from_rotation_arc.

The inputs do not need to be unit vectors however they must be non-zero.

§Panics

Will panic if self or rhs has zero length when glam_assert is enabled.

Source

pub fn angle_to(self, rhs: Vec3, axis: Vec3) -> f32

Returns the signed angle (in radians) from self to rhs around axis in the range [-π, +π].

The axis must be a unit vector. The angle follows the right-hand rule around axis and can be used with Self::rotate_axis, e.g. self.rotate_axis(axis, self.angle_to(rhs, axis)) will be equal to rhs.

For the unsigned angle without a reference axis, see Self::angle_between.

The inputs do not need to be unit vectors however they must be non-zero.

§Panics

Will panic if axis is not normalized when glam_assert is enabled. Will panic if self or rhs has zero length when glam_assert is enabled.

Source

pub fn rotate_x(self, angle: f32) -> Vec3

Rotates around the x axis by angle (in radians).

Source

pub fn rotate_y(self, angle: f32) -> Vec3

Rotates around the y axis by angle (in radians).

Source

pub fn rotate_z(self, angle: f32) -> Vec3

Rotates around the z axis by angle (in radians).

Source

pub fn rotate_axis(self, axis: Vec3, angle: f32) -> Vec3

Rotates around axis by angle (in radians).

The axis must be a unit vector.

§Panics

Will panic if axis is not normalized when glam_assert is enabled.

Source

pub fn rotate_towards(self, rhs: Vec3, max_angle: f32) -> Vec3

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.

Source

pub fn any_orthogonal_vector(self) -> Vec3

Returns some vector that is orthogonal to the given one.

The input vector must be finite and non-zero.

The output vector is not necessarily unit length. For that use Self::any_orthonormal_vector() instead.

Source

pub fn any_orthonormal_vector(self) -> Vec3

Returns any unit vector that is orthogonal to the given one.

The input vector must be unit length.

§Panics

Will panic if self is not normalized when glam_assert is enabled.

Source

pub fn any_orthonormal_pair(self) -> (Vec3, Vec3)

Given a unit vector return two other vectors that together form a right-handed orthonormal basis. That is, all three vectors are orthogonal to each other and are normalized.

§Panics

Will panic if self is not normalized when glam_assert is enabled.

Source

pub fn slerp(self, rhs: Vec3, s: f32) -> Vec3

Performs a spherical 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.

Source

pub fn as_dvec3(self) -> DVec3

Casts all elements of self to f64.

Source

pub fn as_i8vec3(self) -> I8Vec3

Casts all elements of self to i8.

Source

pub fn as_u8vec3(self) -> U8Vec3

Casts all elements of self to u8.

Source

pub fn as_i16vec3(self) -> I16Vec3

Casts all elements of self to i16.

Source

pub fn as_u16vec3(self) -> U16Vec3

Casts all elements of self to u16.

Source

pub fn as_ivec3(self) -> IVec3

Casts all elements of self to i32.

Source

pub fn as_uvec3(self) -> UVec3

Casts all elements of self to u32.

Source

pub fn as_i64vec3(self) -> I64Vec3

Casts all elements of self to i64.

Source

pub fn as_u64vec3(self) -> U64Vec3

Casts all elements of self to u64.

Source

pub fn as_isizevec3(self) -> ISizeVec3

Casts all elements of self to isize.

Source

pub fn as_usizevec3(self) -> USizeVec3

Casts all elements of self to usize.

Trait Implementations§

Source§

impl Add for Vec3

Source§

type Output = Vec3

The resulting type after applying the + operator.
Source§

fn add(self, rhs: Vec3) -> Vec3

Performs the + operation. Read more
Source§

impl Add<&Vec3> for Vec3

Source§

type Output = Vec3

The resulting type after applying the + operator.
Source§

fn add(self, rhs: &Vec3) -> Vec3

Performs the + operation. Read more
Source§

impl Add<&Vec3> for &Vec3

Source§

type Output = Vec3

The resulting type after applying the + operator.
Source§

fn add(self, rhs: &Vec3) -> Vec3

Performs the + operation. Read more
Source§

impl Add<&f32> for Vec3

Source§

type Output = Vec3

The resulting type after applying the + operator.
Source§

fn add(self, rhs: &f32) -> Vec3

Performs the + operation. Read more
Source§

impl Add<&f32> for &Vec3

Source§

type Output = Vec3

The resulting type after applying the + operator.
Source§

fn add(self, rhs: &f32) -> Vec3

Performs the + operation. Read more
Source§

impl Add<Vec3> for &Vec3

Source§

type Output = Vec3

The resulting type after applying the + operator.
Source§

fn add(self, rhs: Vec3) -> Vec3

Performs the + operation. Read more
Source§

impl Add<f32> for Vec3

Source§

type Output = Vec3

The resulting type after applying the + operator.
Source§

fn add(self, rhs: f32) -> Vec3

Performs the + operation. Read more
Source§

impl Add<f32> for &Vec3

Source§

type Output = Vec3

The resulting type after applying the + operator.
Source§

fn add(self, rhs: f32) -> Vec3

Performs the + operation. Read more
Source§

impl AddAssign for Vec3

Source§

fn add_assign(&mut self, rhs: Vec3)

Performs the += operation. Read more
Source§

impl AddAssign<&Vec3> for Vec3

Source§

fn add_assign(&mut self, rhs: &Vec3)

Performs the += operation. Read more
Source§

impl AddAssign<&f32> for Vec3

Source§

fn add_assign(&mut self, rhs: &f32)

Performs the += operation. Read more
Source§

impl AddAssign<f32> for Vec3

Source§

fn add_assign(&mut self, rhs: f32)

Performs the += operation. Read more
Source§

impl AsMut<[f32; 3]> for Vec3

Source§

fn as_mut(&mut self) -> &mut [f32; 3]

Converts this type into a mutable reference of the (usually inferred) input type.
Source§

impl AsRef<[f32; 3]> for Vec3

Source§

fn as_ref(&self) -> &[f32; 3]

Converts this type into a shared reference of the (usually inferred) input type.
Source§

impl Clone for Vec3

Source§

fn clone(&self) -> Vec3

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Copy for Vec3

Source§

impl Debug for Vec3

Source§

fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Default for Vec3

Source§

fn default() -> Vec3

Returns the “default value” for a type. Read more
Source§

impl Display for Vec3

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Div for Vec3

Source§

type Output = Vec3

The resulting type after applying the / operator.
Source§

fn div(self, rhs: Vec3) -> Vec3

Performs the / operation. Read more
Source§

impl Div<&Vec3> for Vec3

Source§

type Output = Vec3

The resulting type after applying the / operator.
Source§

fn div(self, rhs: &Vec3) -> Vec3

Performs the / operation. Read more
Source§

impl Div<&Vec3> for &Vec3

Source§

type Output = Vec3

The resulting type after applying the / operator.
Source§

fn div(self, rhs: &Vec3) -> Vec3

Performs the / operation. Read more
Source§

impl Div<&f32> for Vec3

Source§

type Output = Vec3

The resulting type after applying the / operator.
Source§

fn div(self, rhs: &f32) -> Vec3

Performs the / operation. Read more
Source§

impl Div<&f32> for &Vec3

Source§

type Output = Vec3

The resulting type after applying the / operator.
Source§

fn div(self, rhs: &f32) -> Vec3

Performs the / operation. Read more
Source§

impl Div<Vec3> for &Vec3

Source§

type Output = Vec3

The resulting type after applying the / operator.
Source§

fn div(self, rhs: Vec3) -> Vec3

Performs the / operation. Read more
Source§

impl Div<f32> for Vec3

Source§

type Output = Vec3

The resulting type after applying the / operator.
Source§

fn div(self, rhs: f32) -> Vec3

Performs the / operation. Read more
Source§

impl Div<f32> for &Vec3

Source§

type Output = Vec3

The resulting type after applying the / operator.
Source§

fn div(self, rhs: f32) -> Vec3

Performs the / operation. Read more
Source§

impl DivAssign for Vec3

Source§

fn div_assign(&mut self, rhs: Vec3)

Performs the /= operation. Read more
Source§

impl DivAssign<&Vec3> for Vec3

Source§

fn div_assign(&mut self, rhs: &Vec3)

Performs the /= operation. Read more
Source§

impl DivAssign<&f32> for Vec3

Source§

fn div_assign(&mut self, rhs: &f32)

Performs the /= operation. Read more
Source§

impl DivAssign<f32> for Vec3

Source§

fn div_assign(&mut self, rhs: f32)

Performs the /= operation. Read more
Source§

impl From<(Vec2, f32)> for Vec3

Source§

fn from(_: (Vec2, f32)) -> Vec3

Converts to this type from the input type.
Source§

impl From<(f32, f32, f32)> for Vec3

Source§

fn from(t: (f32, f32, f32)) -> Vec3

Converts to this type from the input type.
Source§

impl From<BVec3> for Vec3

Source§

fn from(v: BVec3) -> Vec3

Converts to this type from the input type.
Source§

impl From<BVec3A> for Vec3

Source§

fn from(v: BVec3A) -> Vec3

Converts to this type from the input type.
Source§

impl From<Vec3> for Vec3A

Source§

fn from(v: Vec3) -> Vec3A

Converts to this type from the input type.
Source§

impl From<Vec3> for DVec3

Source§

fn from(v: Vec3) -> DVec3

Converts to this type from the input type.
Source§

impl From<Vec3> for Transform

Source§

fn from(translation: Vec3) -> Self

A position.

Source§

impl From<Vec3A> for Vec3

Source§

fn from(v: Vec3A) -> Vec3

Converts to this type from the input type.
Source§

impl From<[f32; 3]> for Vec3

Source§

fn from(a: [f32; 3]) -> Vec3

Converts to this type from the input type.
Source§

impl Index<usize> for Vec3

Source§

type Output = f32

The returned type after indexing.
Source§

fn index(&self, index: usize) -> &<Vec3 as Index<usize>>::Output

Performs the indexing (container[index]) operation. Read more
Source§

impl IndexMut<usize> for Vec3

Source§

fn index_mut(&mut self, index: usize) -> &mut <Vec3 as Index<usize>>::Output

Performs the mutable indexing (container[index]) operation. Read more
Source§

impl Mul for Vec3

Source§

type Output = Vec3

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: Vec3) -> Vec3

Performs the * operation. Read more
Source§

impl Mul<&Vec3> for Mat3

Source§

type Output = Vec3

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Vec3) -> Vec3

Performs the * operation. Read more
Source§

impl Mul<&Vec3> for &Mat3

Source§

type Output = Vec3

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Vec3) -> Vec3

Performs the * operation. Read more
Source§

impl Mul<&Vec3> for Vec3

Source§

type Output = Vec3

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Vec3) -> Vec3

Performs the * operation. Read more
Source§

impl Mul<&Vec3> for &Vec3

Source§

type Output = Vec3

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Vec3) -> Vec3

Performs the * operation. Read more
Source§

impl Mul<&Vec3> for Mat3A

Source§

type Output = Vec3

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Vec3) -> Vec3

Performs the * operation. Read more
Source§

impl Mul<&Vec3> for &Mat3A

Source§

type Output = Vec3

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Vec3) -> Vec3

Performs the * operation. Read more
Source§

impl Mul<&Vec3> for Quat

Source§

type Output = Vec3

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Vec3) -> Vec3

Performs the * operation. Read more
Source§

impl Mul<&Vec3> for &Quat

Source§

type Output = Vec3

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &Vec3) -> Vec3

Performs the * operation. Read more
Source§

impl Mul<&f32> for Vec3

Source§

type Output = Vec3

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &f32) -> Vec3

Performs the * operation. Read more
Source§

impl Mul<&f32> for &Vec3

Source§

type Output = Vec3

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: &f32) -> Vec3

Performs the * operation. Read more
Source§

impl Mul<Vec3> for Mat3

Source§

type Output = Vec3

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: Vec3) -> <Mat3 as Mul<Vec3>>::Output

Performs the * operation. Read more
Source§

impl Mul<Vec3> for &Mat3

Source§

type Output = Vec3

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: Vec3) -> Vec3

Performs the * operation. Read more
Source§

impl Mul<Vec3> for &Vec3

Source§

type Output = Vec3

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: Vec3) -> Vec3

Performs the * operation. Read more
Source§

impl Mul<Vec3> for Mat3A

Source§

type Output = Vec3

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: Vec3) -> Vec3

Performs the * operation. Read more
Source§

impl Mul<Vec3> for &Mat3A

Source§

type Output = Vec3

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: Vec3) -> Vec3

Performs the * operation. Read more
Source§

impl Mul<Vec3> for Quat

Source§

fn mul(self, rhs: Vec3) -> <Quat as Mul<Vec3>>::Output

Multiplies a quaternion and a 3D vector, returning the rotated vector.

§Panics

Will panic if self is not normalized when glam_assert is enabled.

Source§

type Output = Vec3

The resulting type after applying the * operator.
Source§

impl Mul<Vec3> for &Quat

Source§

type Output = Vec3

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: Vec3) -> Vec3

Performs the * operation. Read more
Source§

impl Mul<f32> for Vec3

Source§

type Output = Vec3

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: f32) -> Vec3

Performs the * operation. Read more
Source§

impl Mul<f32> for &Vec3

Source§

type Output = Vec3

The resulting type after applying the * operator.
Source§

fn mul(self, rhs: f32) -> Vec3

Performs the * operation. Read more
Source§

impl MulAssign for Vec3

Source§

fn mul_assign(&mut self, rhs: Vec3)

Performs the *= operation. Read more
Source§

impl MulAssign<&Vec3> for Vec3

Source§

fn mul_assign(&mut self, rhs: &Vec3)

Performs the *= operation. Read more
Source§

impl MulAssign<&f32> for Vec3

Source§

fn mul_assign(&mut self, rhs: &f32)

Performs the *= operation. Read more
Source§

impl MulAssign<f32> for Vec3

Source§

fn mul_assign(&mut self, rhs: f32)

Performs the *= operation. Read more
Source§

impl Neg for Vec3

Source§

type Output = Vec3

The resulting type after applying the - operator.
Source§

fn neg(self) -> Vec3

Performs the unary - operation. Read more
Source§

impl Neg for &Vec3

Source§

type Output = Vec3

The resulting type after applying the - operator.
Source§

fn neg(self) -> Vec3

Performs the unary - operation. Read more
Source§

impl PartialEq for Vec3

Source§

fn eq(&self, other: &Vec3) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl Pod for Vec3

Source§

impl Product for Vec3

Source§

fn product<I>(iter: I) -> Vec3
where I: Iterator<Item = Vec3>,

Takes an iterator and generates Self from the elements by multiplying the items.
Source§

impl<'a> Product<&'a Vec3> for Vec3

Source§

fn product<I>(iter: I) -> Vec3
where I: Iterator<Item = &'a Vec3>,

Takes an iterator and generates Self from the elements by multiplying the items.
Source§

impl Rem for Vec3

Source§

type Output = Vec3

The resulting type after applying the % operator.
Source§

fn rem(self, rhs: Vec3) -> Vec3

Performs the % operation. Read more
Source§

impl Rem<&Vec3> for Vec3

Source§

type Output = Vec3

The resulting type after applying the % operator.
Source§

fn rem(self, rhs: &Vec3) -> Vec3

Performs the % operation. Read more
Source§

impl Rem<&Vec3> for &Vec3

Source§

type Output = Vec3

The resulting type after applying the % operator.
Source§

fn rem(self, rhs: &Vec3) -> Vec3

Performs the % operation. Read more
Source§

impl Rem<&f32> for Vec3

Source§

type Output = Vec3

The resulting type after applying the % operator.
Source§

fn rem(self, rhs: &f32) -> Vec3

Performs the % operation. Read more
Source§

impl Rem<&f32> for &Vec3

Source§

type Output = Vec3

The resulting type after applying the % operator.
Source§

fn rem(self, rhs: &f32) -> Vec3

Performs the % operation. Read more
Source§

impl Rem<Vec3> for &Vec3

Source§

type Output = Vec3

The resulting type after applying the % operator.
Source§

fn rem(self, rhs: Vec3) -> Vec3

Performs the % operation. Read more
Source§

impl Rem<f32> for Vec3

Source§

type Output = Vec3

The resulting type after applying the % operator.
Source§

fn rem(self, rhs: f32) -> Vec3

Performs the % operation. Read more
Source§

impl Rem<f32> for &Vec3

Source§

type Output = Vec3

The resulting type after applying the % operator.
Source§

fn rem(self, rhs: f32) -> Vec3

Performs the % operation. Read more
Source§

impl RemAssign for Vec3

Source§

fn rem_assign(&mut self, rhs: Vec3)

Performs the %= operation. Read more
Source§

impl RemAssign<&Vec3> for Vec3

Source§

fn rem_assign(&mut self, rhs: &Vec3)

Performs the %= operation. Read more
Source§

impl RemAssign<&f32> for Vec3

Source§

fn rem_assign(&mut self, rhs: &f32)

Performs the %= operation. Read more
Source§

impl RemAssign<f32> for Vec3

Source§

fn rem_assign(&mut self, rhs: f32)

Performs the %= operation. Read more
Source§

impl StructuralPartialEq for Vec3

Source§

impl Sub for Vec3

Source§

type Output = Vec3

The resulting type after applying the - operator.
Source§

fn sub(self, rhs: Vec3) -> Vec3

Performs the - operation. Read more
Source§

impl Sub<&Vec3> for Vec3

Source§

type Output = Vec3

The resulting type after applying the - operator.
Source§

fn sub(self, rhs: &Vec3) -> Vec3

Performs the - operation. Read more
Source§

impl Sub<&Vec3> for &Vec3

Source§

type Output = Vec3

The resulting type after applying the - operator.
Source§

fn sub(self, rhs: &Vec3) -> Vec3

Performs the - operation. Read more
Source§

impl Sub<&f32> for Vec3

Source§

type Output = Vec3

The resulting type after applying the - operator.
Source§

fn sub(self, rhs: &f32) -> Vec3

Performs the - operation. Read more
Source§

impl Sub<&f32> for &Vec3

Source§

type Output = Vec3

The resulting type after applying the - operator.
Source§

fn sub(self, rhs: &f32) -> Vec3

Performs the - operation. Read more
Source§

impl Sub<Vec3> for &Vec3

Source§

type Output = Vec3

The resulting type after applying the - operator.
Source§

fn sub(self, rhs: Vec3) -> Vec3

Performs the - operation. Read more
Source§

impl Sub<f32> for Vec3

Source§

type Output = Vec3

The resulting type after applying the - operator.
Source§

fn sub(self, rhs: f32) -> Vec3

Performs the - operation. Read more
Source§

impl Sub<f32> for &Vec3

Source§

type Output = Vec3

The resulting type after applying the - operator.
Source§

fn sub(self, rhs: f32) -> Vec3

Performs the - operation. Read more
Source§

impl SubAssign for Vec3

Source§

fn sub_assign(&mut self, rhs: Vec3)

Performs the -= operation. Read more
Source§

impl SubAssign<&Vec3> for Vec3

Source§

fn sub_assign(&mut self, rhs: &Vec3)

Performs the -= operation. Read more
Source§

impl SubAssign<&f32> for Vec3

Source§

fn sub_assign(&mut self, rhs: &f32)

Performs the -= operation. Read more
Source§

impl SubAssign<f32> for Vec3

Source§

fn sub_assign(&mut self, rhs: f32)

Performs the -= operation. Read more
Source§

impl Sum for Vec3

Source§

fn sum<I>(iter: I) -> Vec3
where I: Iterator<Item = Vec3>,

Takes an iterator and generates Self from the elements by “summing up” the items.
Source§

impl<'a> Sum<&'a Vec3> for Vec3

Source§

fn sum<I>(iter: I) -> Vec3
where I: Iterator<Item = &'a Vec3>,

Takes an iterator and generates Self from the elements by “summing up” the items.
Source§

impl Vec3Swizzles for Vec3

Source§

type Vec2 = Vec2

Source§

type Vec4 = Vec4

Source§

fn xx(self) -> Vec2

Source§

fn xy(self) -> Vec2

Source§

fn with_xy(self, rhs: Vec2) -> Vec3

Source§

fn xz(self) -> Vec2

Source§

fn with_xz(self, rhs: Vec2) -> Vec3

Source§

fn yx(self) -> Vec2

Source§

fn with_yx(self, rhs: Vec2) -> Vec3

Source§

fn yy(self) -> Vec2

Source§

fn yz(self) -> Vec2

Source§

fn with_yz(self, rhs: Vec2) -> Vec3

Source§

fn zx(self) -> Vec2

Source§

fn with_zx(self, rhs: Vec2) -> Vec3

Source§

fn zy(self) -> Vec2

Source§

fn with_zy(self, rhs: Vec2) -> Vec3

Source§

fn zz(self) -> Vec2

Source§

fn xxx(self) -> Vec3

Source§

fn xxy(self) -> Vec3

Source§

fn xxz(self) -> Vec3

Source§

fn xyx(self) -> Vec3

Source§

fn xyy(self) -> Vec3

Source§

fn xzx(self) -> Vec3

Source§

fn xzy(self) -> Vec3

Source§

fn xzz(self) -> Vec3

Source§

fn yxx(self) -> Vec3

Source§

fn yxy(self) -> Vec3

Source§

fn yxz(self) -> Vec3

Source§

fn yyx(self) -> Vec3

Source§

fn yyy(self) -> Vec3

Source§

fn yyz(self) -> Vec3

Source§

fn yzx(self) -> Vec3

Source§

fn yzy(self) -> Vec3

Source§

fn yzz(self) -> Vec3

Source§

fn zxx(self) -> Vec3

Source§

fn zxy(self) -> Vec3

Source§

fn zxz(self) -> Vec3

Source§

fn zyx(self) -> Vec3

Source§

fn zyy(self) -> Vec3

Source§

fn zyz(self) -> Vec3

Source§

fn zzx(self) -> Vec3

Source§

fn zzy(self) -> Vec3

Source§

fn zzz(self) -> Vec3

Source§

fn xxxx(self) -> Vec4

Source§

fn xxxy(self) -> Vec4

Source§

fn xxxz(self) -> Vec4

Source§

fn xxyx(self) -> Vec4

Source§

fn xxyy(self) -> Vec4

Source§

fn xxyz(self) -> Vec4

Source§

fn xxzx(self) -> Vec4

Source§

fn xxzy(self) -> Vec4

Source§

fn xxzz(self) -> Vec4

Source§

fn xyxx(self) -> Vec4

Source§

fn xyxy(self) -> Vec4

Source§

fn xyxz(self) -> Vec4

Source§

fn xyyx(self) -> Vec4

Source§

fn xyyy(self) -> Vec4

Source§

fn xyyz(self) -> Vec4

Source§

fn xyzx(self) -> Vec4

Source§

fn xyzy(self) -> Vec4

Source§

fn xyzz(self) -> Vec4

Source§

fn xzxx(self) -> Vec4

Source§

fn xzxy(self) -> Vec4

Source§

fn xzxz(self) -> Vec4

Source§

fn xzyx(self) -> Vec4

Source§

fn xzyy(self) -> Vec4

Source§

fn xzyz(self) -> Vec4

Source§

fn xzzx(self) -> Vec4

Source§

fn xzzy(self) -> Vec4

Source§

fn xzzz(self) -> Vec4

Source§

fn yxxx(self) -> Vec4

Source§

fn yxxy(self) -> Vec4

Source§

fn yxxz(self) -> Vec4

Source§

fn yxyx(self) -> Vec4

Source§

fn yxyy(self) -> Vec4

Source§

fn yxyz(self) -> Vec4

Source§

fn yxzx(self) -> Vec4

Source§

fn yxzy(self) -> Vec4

Source§

fn yxzz(self) -> Vec4

Source§

fn yyxx(self) -> Vec4

Source§

fn yyxy(self) -> Vec4

Source§

fn yyxz(self) -> Vec4

Source§

fn yyyx(self) -> Vec4

Source§

fn yyyy(self) -> Vec4

Source§

fn yyyz(self) -> Vec4

Source§

fn yyzx(self) -> Vec4

Source§

fn yyzy(self) -> Vec4

Source§

fn yyzz(self) -> Vec4

Source§

fn yzxx(self) -> Vec4

Source§

fn yzxy(self) -> Vec4

Source§

fn yzxz(self) -> Vec4

Source§

fn yzyx(self) -> Vec4

Source§

fn yzyy(self) -> Vec4

Source§

fn yzyz(self) -> Vec4

Source§

fn yzzx(self) -> Vec4

Source§

fn yzzy(self) -> Vec4

Source§

fn yzzz(self) -> Vec4

Source§

fn zxxx(self) -> Vec4

Source§

fn zxxy(self) -> Vec4

Source§

fn zxxz(self) -> Vec4

Source§

fn zxyx(self) -> Vec4

Source§

fn zxyy(self) -> Vec4

Source§

fn zxyz(self) -> Vec4

Source§

fn zxzx(self) -> Vec4

Source§

fn zxzy(self) -> Vec4

Source§

fn zxzz(self) -> Vec4

Source§

fn zyxx(self) -> Vec4

Source§

fn zyxy(self) -> Vec4

Source§

fn zyxz(self) -> Vec4

Source§

fn zyyx(self) -> Vec4

Source§

fn zyyy(self) -> Vec4

Source§

fn zyyz(self) -> Vec4

Source§

fn zyzx(self) -> Vec4

Source§

fn zyzy(self) -> Vec4

Source§

fn zyzz(self) -> Vec4

Source§

fn zzxx(self) -> Vec4

Source§

fn zzxy(self) -> Vec4

Source§

fn zzxz(self) -> Vec4

Source§

fn zzyx(self) -> Vec4

Source§

fn zzyy(self) -> Vec4

Source§

fn zzyz(self) -> Vec4

Source§

fn zzzx(self) -> Vec4

Source§

fn zzzy(self) -> Vec4

Source§

fn zzzz(self) -> Vec4

Source§

fn xyz(self) -> Self

Source§

impl Zeroable for Vec3

Source§

fn zeroed() -> Self

Auto Trait Implementations§

§

impl Freeze for Vec3

§

impl RefUnwindSafe for Vec3

§

impl Send for Vec3

§

impl Sync for Vec3

§

impl Unpin for Vec3

§

impl UnsafeUnpin for Vec3

§

impl UnwindSafe for Vec3

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> AnyBitPattern for T
where T: Pod,

Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> CheckedBitPattern for T
where T: AnyBitPattern,

Source§

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

If this function returns true, then it must be valid to reinterpret bits as &Self.
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> Downcast for T
where T: Any,

Source§

fn into_any(self: Box<T>) -> Box<dyn Any>

Convert Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.
Source§

fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>

Convert Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be further downcast into Rc<ConcreteType> where ConcreteType implements Trait.
Source§

fn as_any(&self) -> &(dyn Any + 'static)

Convert &Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &Any’s vtable from &Trait’s.
Source§

fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)

Convert &mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &mut Any’s vtable from &mut Trait’s.
Source§

impl<T> Downcast<T> for T

Source§

fn downcast(&self) -> &T

Source§

impl<T> DowncastSync for T
where T: Any + Send + Sync,

Source§

fn into_any_arc(self: Arc<T>) -> Arc<dyn Any + Sync + Send>

Convert Arc<Trait> (where Trait: Downcast) to Arc<Any>. Arc<Any> can then be further downcast into Arc<ConcreteType> where ConcreteType implements Trait.
Source§

impl<S, T> Duplex<S> for T
where T: FromSample<S> + ToSample<S>,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<S> FromSample<S> for S

Source§

fn from_sample_(s: S) -> S

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> NoUninit for T
where T: Pod,

Source§

impl<T, Rhs> NumAssignOps<Rhs> for T
where T: AddAssign<Rhs> + SubAssign<Rhs> + MulAssign<Rhs> + DivAssign<Rhs> + RemAssign<Rhs>,

Source§

impl<T, Rhs, Output> NumOps<Rhs, Output> for T
where T: Sub<Rhs, Output = Output> + Mul<Rhs, Output = Output> + Div<Rhs, Output = Output> + Add<Rhs, Output = Output> + Rem<Rhs, Output = Output>,

Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T, Base> RefNum<Base> for T
where T: NumOps<Base, Base> + for<'r> NumOps<&'r Base, Base>,

Source§

impl<T> SerializableAny for T
where T: 'static + Any + Clone + for<'a> Send + Sync,

Source§

impl<T, S> SimdFrom<T, S> for T
where S: Simd,

Source§

fn simd_from(_simd: S, value: T) -> T

Source§

impl<F, T, S> SimdInto<T, S> for F
where T: SimdFrom<F, S>, S: Simd,

Source§

fn simd_into(self, simd: S) -> T

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> ToSample<U> for T
where U: FromSample<T>,

Source§

fn to_sample_(self) -> U

Source§

impl<T> ToSmolStr for T
where T: Display + ?Sized,

Source§

impl<T> ToString for T
where T: Display + ?Sized,

Source§

fn to_string(&self) -> String

Converts the given value to a String. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> Upcast<T> for T

Source§

fn upcast(&self) -> Option<&T>

Source§

impl<T> WasmNotSend for T
where T: Send,

Source§

impl<T> WasmNotSendSync for T

Source§

impl<T> WasmNotSync for T
where T: Sync,

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more