Skip to main content

Color

Struct Color 

Source
#[repr(C)]
pub struct Color { pub red: f32, pub green: f32, pub blue: f32, pub alpha: f32, }
Expand description

A red, green, blue, and opacity color, each a linear value the engine encodes to sRGB on its way to the window.

Nothing clamps a channel: a value past 1.0 is light the frame holds and the tone map brings down, which is what an emissive material or a bright light is written with. 0.0..=1.0 is what the window can show.

Values are linear; the engine writes sRGB-encoded values to the screen. A color channel past 1.0 holds light no screen draws, which FrameContext::set_bloom spreads.

Fields§

§red: f32

Red.

§green: f32

Green.

§blue: f32

Blue.

§alpha: f32

Opacity: 1.0 is fully opaque, 0.0 is empty.

Implementations§

Source§

impl Color

Source

pub const BLACK: Self

Opaque black.

Source

pub const WHITE: Self

Opaque white.

Source

pub const fn rgb(red: f32, green: f32, blue: f32) -> Self

An opaque color, each channel a fraction of 1.0.

Examples found in repository?
examples/post-effects.rs (line 15)
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);
More examples
Hide additional examples
examples/sprite-adventure.rs (line 261)
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);
examples/stress-preview.rs (line 51)
51const ROCK_COLOR: Color = Color::rgb(0.42, 0.4, 0.38);
52const GROUND_COLOR: Color = Color::rgb(0.16, 0.17, 0.14);
53
54const SUN_DIRECTION: Vec3 = Vec3::new(-0.35, -1.0, -0.5);
55const SUN_COLOR: Color = Color::rgb(0.95, 0.92, 0.85);
56
57/// The sky's own zenith, horizon and nadir colors, and the fraction of its
58/// own light it lands on top of the sun's, held low so the sun's shadows
59/// still read.
60const SKY_ZENITH: Color = Color::rgb(0.25, 0.4, 0.65);
61const SKY_HORIZON: Color = Color::rgb(0.75, 0.72, 0.62);
62const SKY_NADIR: Color = Color::rgb(0.12, 0.12, 0.1);
examples/isometric-board.rs (line 82)
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    }
examples/sound-lab.rs (line 47)
47const FLOOR_COLOR: Color = Color::rgb(0.14, 0.14, 0.17);
48const WALL_COLOR: Color = Color::rgb(0.22, 0.24, 0.30);
49const SUN_COLOR: Color = Color::rgb(0.85, 0.85, 0.90);
50const LISTENER_COLOR: Color = Color::rgb(0.85, 0.85, 0.75);
51/// Red on the right, white on the left: the pair the engine's own pan
52/// reads, set on the ears so the sides are distinct.
53const RIGHT_EAR_COLOR: Color = Color::rgb(0.85, 0.2, 0.2);
54const LEFT_EAR_COLOR: Color = Color::rgb(0.92, 0.92, 0.88);
55const SOURCE_COLORS: [Color; 3] = [
56    Color::rgb(0.85, 0.35, 0.35),
57    Color::rgb(0.35, 0.75, 0.85),
58    Color::rgb(0.85, 0.75, 0.30),
59];
60const RANGE_COLOR: Color = Color::rgba(1.0, 1.0, 1.0, 0.35);
61/// The inner ring drawn around a source, where its level stops being full.
62const REFERENCE_COLOR: Color = Color::rgba(1.0, 0.85, 0.35, 0.5);
63
64const SKY_ZENITH: Color = Color::rgb(0.10, 0.11, 0.16);
65const SKY_HORIZON: Color = Color::rgb(0.20, 0.20, 0.24);
66const SKY_NADIR: Color = Color::rgb(0.06, 0.06, 0.08);
67/// The fraction of its own light the sky lands and reflects: dim, so the
68/// cubes' own glow and the room's light still read.
69const SKY_LIGHT: f32 = 0.2;
70
71/// Two fixed positions holding the same clip at the default instance,
72/// read only while [`SoundCheck::merge_demo`] is set.
73const MERGE_POS_A: Vec3 = Vec3::new(-4.0, SOURCE_HEIGHT, 4.5);
74const MERGE_POS_B: Vec3 = Vec3::new(4.0, SOURCE_HEIGHT, 4.5);
75const MERGE_GAIN: f32 = 0.5;
76const MERGE_COLOR_A: Color = Color::rgb(0.95, 0.55, 0.15);
77const MERGE_COLOR_B: Color = Color::rgb(0.55, 0.4, 0.85);
78
79/// Where [`Sound::Theme`] and [`Sound::ThemeDecoded`] loop from once
80/// declared: a few seconds short of the end, so a wrap seeks across most of
81/// the clip.
82const THEME_LOOP_FROM: Duration = Duration::from_secs(130);
83
84/// Count of sustains the ring declares at once: more than the engine plays,
85/// so the least loud of them hold no voice.
86const RING_COUNT: u32 = MAX_VOICES as u32 + 8;
87/// Radius of the ring they stand on.
88const RING_RADIUS: f32 = 4.6;
89/// Distance the ring's sustains hold their full level within: over half the
90/// radius, so the whole ring is heard from the middle of the room.
91const RING_REFERENCE: f32 = 2.5;
92/// Gain the loudest of them takes; each one after it is less loud, so the cut
93/// falls inside the ring.
94const RING_GAIN: f32 = 0.35;
95/// Color of the ring's cubes, taken less bright the less loud the sustain a
96/// cube stands for.
97const RING_COLOR: Color = Color::rgb(0.35, 0.75, 0.95);
98
99/// Every source this example loads, next to `index.html` on the web and
100/// under the working directory on the desktop.
101const ASSET_FILES: [&str; 9] = [
102    "examples/assets/bounce.ogg",
103    "examples/assets/break.ogg",
104    "examples/assets/serve.ogg",
105    "examples/assets/gameover.ogg",
106    "examples/assets/lost.ogg",
107    "examples/assets/win.ogg",
108    "examples/assets/click.ogg",
109    "examples/assets/music.ogg",
110    "examples/assets/menu_music.ogg",
111];
112
113fn main() {
114    run(
115        Config::new("Mirage: sound lab")
116            .with_size(1280, 720)
117            .with_assets(ASSET_FILES),
118        SoundCheck::init,
119    );
120}
121
122/// The one sky this room draws, a gradient set each frame.
123#[derive(Catalog, Clone, Copy, Debug, Eq, Hash, PartialEq)]
124enum Sky {
125    Room,
126}
127
128impl Skyboxes for Sky {
129    fn build(&self, _assets: &Assets) -> SkyboxData {
130        match self {
131            Self::Room => {
132                SkyboxData::gradient(SKY_ZENITH, SKY_HORIZON, SKY_NADIR).lit_by(SKY_LIGHT)
133            }
134        }
135    }
136}
137
138/// A source's reference or its range: a flat ring on the ground,
139/// unit-sized, drawn that many meters across by its scale.
140#[derive(Catalog, Clone, PartialEq, Eq, Hash)]
141struct Ring;
142
143impl Mesh for Ring {
144    fn build(&self, _: &Assets) -> MeshData {
145        ring_outline()
146    }
147}
148
149/// The listener's facing marker: a point through `-Z`, unit-sized.
150#[derive(Catalog, Clone, PartialEq, Eq, Hash)]
151struct Facing;
152
153impl Mesh for Facing {
154    fn build(&self, _: &Assets) -> MeshData {
155        facing_marker()
156    }
157}
158
159// Everything else this game draws is a built-in primitive, given its
160// color and placed per draw: the room's floor and walls, a cube drawn for
161// a source or the listener, and the listener's ears.
162meshes! { enum Shape { Plane, Cube, Ring, Sphere, Facing } }
163
164fn ring_outline() -> MeshData {
165    const SEGMENTS: u32 = 48;
166    const OUTER: f32 = 1.0;
167    const INNER: f32 = 0.94;
168
169    let mut vertices = Vec::with_capacity(SEGMENTS as usize * 4);
170    let mut indices = Vec::with_capacity(SEGMENTS as usize * 6);
171    for segment in 0..SEGMENTS {
172        let a0 = segment as f32 / SEGMENTS as f32 * TAU;
173        let a1 = (segment + 1) as f32 / SEGMENTS as f32 * TAU;
174        let (u0, v0) = (a0.cos(), a0.sin());
175        let (u1, v1) = (a1.cos(), a1.sin());
176        let base = vertices.len() as u32;
177        vertices.extend([
178            Vertex::new(Vec3::new(INNER * u0, 0.0, -INNER * v0), Vec3::Y, Vec2::ZERO),
179            Vertex::new(Vec3::new(OUTER * u0, 0.0, -OUTER * v0), Vec3::Y, Vec2::ZERO),
180            Vertex::new(Vec3::new(OUTER * u1, 0.0, -OUTER * v1), Vec3::Y, Vec2::ZERO),
181            Vertex::new(Vec3::new(INNER * u1, 0.0, -INNER * v1), Vec3::Y, Vec2::ZERO),
182        ]);
183        indices.extend([base, base + 1, base + 2, base, base + 2, base + 3]);
184    }
185    MeshData::new(vertices, indices)
186}
187
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    }
examples/material-playground.rs (line 58)
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    }
947
948    /// Three pillars and a pole a light can shadow, beside `Banner`'s
949    /// displaced cloth and `Field`'s pulsing sphere — [`OUTPOST`] moves the
950    /// whole group clear of the rest of the scene.
951    fn draw_outpost(&self, ctx: &mut FrameContext<'_, Self>) {
952        let clock = ctx.elapsed().as_secs_f32();
953
954        for &(position, scale) in &PILLARS {
955            ctx.draw(
956                Cube.at(Transform::from_scale_rotation_translation(
957                    scale,
958                    Quat::IDENTITY,
959                    OUTPOST + position,
960                ))
961                .material(Material::lit(Color::rgb(0.55, 0.5, 0.45))),
962            );
963        }
964
965        ctx.draw(
966            Cube.at(Transform::from_scale_rotation_translation(
967                POLE_SCALE,
968                Quat::IDENTITY,
969                OUTPOST + POLE_POSITION,
970            ))
971            .material(Material::lit(Color::rgb(0.3, 0.24, 0.18))),
972        );
973
974        ctx.set_surface_style(Banner { time: clock });
975        ctx.draw(
976            BannerCloth
977                .at(Transform::from_translation(OUTPOST + BANNER_MOUNT))
978                .material(Material::lit(Color::rgb(0.75, 0.12, 0.12)))
979                .surface_style::<Banner>(),
980        );
981
982        ctx.set_surface_style(Field {
983            tint: Color::rgb(0.25, 0.75, 1.0),
984            time: clock,
985        });
986        ctx.draw(
987            Sphere { subdivisions: 2 }
988                .at(Transform::from_scale_rotation_translation(
989                    Vec3::splat(FIELD_ORB_SCALE),
990                    Quat::IDENTITY,
991                    OUTPOST + FIELD_ORB_POSITION,
992                ))
993                .material(Material::color(Color::BLACK))
994                .surface_style::<Field>(),
995        );
996    }
997
998    /// This frame's sun, lamp and spotlight, each `.shadow()` where its own
999    /// flag is set. The sun's direction, color and strength come from the
1000    /// chosen [`Sky`], and is absent where the choice pairs with none; only
1001    /// its shadow flag is the player's own.
1002    fn lights(&self) -> Vec<Light> {
1003        let lamp = Light::point(LAMP_POSITION, self.lamp.scaled(), LAMP_RANGE);
1004        let spotlight = Light::spot(Spot {
1005            position: SPOT_POSITION,
1006            direction: SPOT_DIRECTION,
1007            color: self.spotlight.scaled(),
1008            range: SPOT_RANGE,
1009            angle: SPOT_ANGLE,
1010        });
1011
1012        let mut lights = vec![
1013            if self.lamp.shadow {
1014                lamp.shadow()
1015            } else {
1016                lamp
1017            },
1018            if self.spotlight.shadow {
1019                spotlight.shadow()
1020            } else {
1021                spotlight
1022            },
1023        ];
1024
1025        if let Some((direction, color, strength)) = self.sky.sun() {
1026            let sun = Light::directional(direction, scaled(color, strength));
1027            lights.push(if self.sun_shadow { sun.shadow() } else { sun });
1028        }
1029
1030        lights
1031    }
1032
1033    /// Every live knob, in a side area of fixed width so the scene stays
1034    /// visible beside it — the default sky reads as flat grey, so a
1035    /// control added straight to the bare UI layer would read as a slab
1036    /// across the window.
1037    fn controls(&mut self, ctx: &mut FrameContext<'_, Self>) {
1038        let tonemap = ctx.config().tonemap();
1039        let antialiasing = ctx.config().antialiasing();
1040        let shadow_resolution = ctx.config().shadow_resolution();
1041
1042        ctx.ui(|ui| {
1043            egui::Panel::right("controls")
1044                .resizable(false)
1045                .default_size(300.0)
1046                .show(ui, |ui| {
1047                    egui::ScrollArea::vertical().show(ui, |ui| {
1048                        ui.label(
1049                            "right mouse button to look, W/A/S/D to move, \
1050                             space/shift up and down, wheel to scale speed",
1051                        );
1052                        ui.separator();
1053                        self.lighting_controls(ui);
1054                        ui.separator();
1055                        self.light_controls(ui);
1056                        ui.separator();
1057                        self.material_controls(ui);
1058                        ui.separator();
1059                        ui.label(format!(
1060                            "tone map {tonemap:?} \u{b7} antialiasing {antialiasing} \u{b7} \
1061                             shadow {shadow_resolution}px: set at startup, not live"
1062                        ));
1063                        ui.add(egui::Slider::new(&mut self.exposure, 0.1..=3.0).text("exposure"));
1064                        ui.add(egui::Slider::new(&mut self.bloom, 0.0..=1.0).text("bloom"));
1065                    });
1066                });
1067        });
1068    }
1069
1070    fn lighting_controls(&mut self, ui: &mut egui::Ui) {
1071        ui.heading("lighting");
1072        for choice in Sky::ALL {
1073            ui.radio_value(&mut self.sky, choice, choice.name());
1074        }
1075        ui.label(format!("sky light {:.2}: set at startup", self.sky.light()));
1076
1077        match self.sky.sun() {
1078            Some((_, color, strength)) => {
1079                color_swatch(ui, "sun", color);
1080                ui.label(format!("sun strength {strength:.2}: set by the choice"));
1081                ui.checkbox(&mut self.sun_shadow, "sun shadow");
1082            }
1083            None => {
1084                ui.label("no sun: set by the choice");
1085            }
1086        }
1087    }
1088
1089    fn light_controls(&mut self, ui: &mut egui::Ui) {
1090        ui.heading("lights");
1091        glow_controls(ui, "lamp", &mut self.lamp);
1092        glow_controls(ui, "spotlight", &mut self.spotlight);
1093    }
1094
1095    fn material_controls(&mut self, ui: &mut egui::Ui) {
1096        ui.heading("material");
1097        color_row(ui, "tint", &mut self.front_tint);
1098        ui.add(egui::Slider::new(&mut self.front_roughness, 0.0..=1.0).text("roughness"));
1099        ui.add(egui::Slider::new(&mut self.front_metallic, 0.0..=1.0).text("metallic"));
1100        ui.checkbox(
1101            &mut self.shading_map_on,
1102            "shading map: occlusion \u{b7} roughness \u{b7} metallic",
1103        );
1104        ui.checkbox(&mut self.relief_map_on, "relief map: bump normals");
1105        ui.checkbox(&mut self.emissive_map_on, "emissive map: per-texel glow");
1106    }
1107}
1108
1109/// One light's own controls: a color, its strength and a shadow flag.
1110fn glow_controls(ui: &mut egui::Ui, label: &str, glow: &mut Glow) {
1111    ui.label(label);
1112    color_row(ui, "color", &mut glow.color);
1113    ui.add(egui::Slider::new(&mut glow.strength, 0.0..=8.0).text("strength"));
1114    ui.checkbox(&mut glow.shadow, "shadow");
1115}
1116
1117/// One named control over `color`'s red, green and blue channels; alpha
1118/// stays `1.0`.
1119fn color_row(ui: &mut egui::Ui, label: &str, color: &mut Color) {
1120    let mut rgb = [color.red, color.green, color.blue];
1121    ui.horizontal(|ui| {
1122        ui.label(label);
1123        if ui.color_edit_button_rgb(&mut rgb).changed() {
1124            *color = Color::rgb(rgb[0], rgb[1], rgb[2]);
1125        }
1126    });
1127}
Source

pub const fn rgba(red: f32, green: f32, blue: f32, alpha: f32) -> Self

A color with opacity alpha, each channel and alpha a fraction of 1.0.

Examples found in repository?
examples/sound-lab.rs (line 60)
60const RANGE_COLOR: Color = Color::rgba(1.0, 1.0, 1.0, 0.35);
61/// The inner ring drawn around a source, where its level stops being full.
62const REFERENCE_COLOR: Color = Color::rgba(1.0, 0.85, 0.35, 0.5);
More examples
Hide additional examples
examples/sprite-adventure.rs (line 174)
174const WATER_COLOR: Color = Color::rgba(0.06, 0.20, 0.27, 0.92);
Source

pub const fn with_alpha(self, alpha: f32) -> Self

The same channels at opacity alpha, a fraction of 1.0.

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

pub const fn dimmed(self, factor: f32) -> Self

Its red, green, and blue scaled by factor, a fraction of each channel’s own value, keeping its opacity.

Examples found in repository?
examples/breakout-game.rs (line 595)
593    fn paddle_face_material(&self) -> Material {
594        let t = (self.paddle_flash / PADDLE_FLASH).clamp(0.0, 1.0);
595        let flash = PADDLE_FLASH_EMISSIVE.dimmed(t);
596        let emissive = Color::rgb(
597            PADDLE_AMBIENT_EMISSIVE.red + flash.red,
598            PADDLE_AMBIENT_EMISSIVE.green + flash.green,
599            PADDLE_AMBIENT_EMISSIVE.blue + flash.blue,
600        );
601        Material::lit(PADDLE_BASE).emissive(emissive)
602    }
603
604    fn draw_court(&self, ctx: &mut FrameContext<'_, Breakout>) {
605        ctx.draw(
606            Plane
607                .at(Transform::from_scale(Vec3::new(
608                    COURT_HALF_WIDTH * 2.0,
609                    1.0,
610                    COURT_HALF_DEPTH * 2.0,
611                )))
612                .material(Material::lit(FLOOR_COLOR)),
613        );
614
615        let side_half = Vec3::new(WALL_THICKNESS * 0.5, WALL_HEIGHT * 0.5, COURT_HALF_DEPTH);
616        for side in [-1.0, 1.0] {
617            let x = side * (COURT_HALF_WIDTH - WALL_THICKNESS * 0.5);
618            ctx.draw(
619                Cube.at(Transform::from_scale_rotation_translation(
620                    side_half * 2.0,
621                    Quat::IDENTITY,
622                    Vec3::new(x, side_half.y, 0.0),
623                ))
624                .material(Material::lit(WALL_COLOR)),
625            );
626        }
627
628        let top_half = Vec3::new(COURT_HALF_WIDTH, WALL_HEIGHT * 0.5, WALL_THICKNESS * 0.5);
629        ctx.draw(
630            Cube.at(Transform::from_scale_rotation_translation(
631                top_half * 2.0,
632                Quat::IDENTITY,
633                Vec3::new(0.0, top_half.y, -COURT_HALF_DEPTH + WALL_THICKNESS * 0.5),
634            ))
635            .material(Material::lit(WALL_COLOR)),
636        );
637    }
638
639    fn draw_bricks(&self, ctx: &mut FrameContext<'_, Breakout>) {
640        let scale = Vec3::new(
641            BRICK_HALF_WIDTH * 2.0,
642            BRICK_HALF_HEIGHT * 2.0,
643            BRICK_HALF_DEPTH * 2.0,
644        );
645        for brick in self.bricks.iter().filter(|brick| brick.hits_remaining > 0) {
646            let health = f32::from(brick.hits_remaining) / f32::from(BRICK_HITS);
647            let color = BRICK_ROW_COLORS[brick.row].dimmed(0.4 + 0.6 * health);
648            ctx.draw(
649                Cube.at(Transform::from_scale_rotation_translation(
650                    scale,
651                    Quat::IDENTITY,
652                    brick.position,
653                ))
654                .material(Material::shaded(color, health)),
655            );
656        }
657    }
658
659    /// Draws the live spark burst: additive, tumbling by roll as they age,
660    /// shrinking and fading out over their lifetime.
661    fn draw_sparks(&self, ctx: &mut FrameContext<'_, Breakout>) {
662        for spark in &self.sparks {
663            let age = (spark.age / SPARK_LIFETIME).clamp(0.0, 1.0);
664            let fade = 1.0 - age;
665            let size = SPARK_SIZE_START.lerp(SPARK_SIZE_END, age);
666            ctx.draw(
667                Quad.at(Transform::from_scale_rotation_translation(
668                    Vec3::splat(size),
669                    Quat::IDENTITY,
670                    spark.position,
671                ))
672                .billboard()
673                .roll(spark.roll + spark.age * SPARK_SPIN_SPEED)
674                .material(
675                    Material::color(spark.color.with_alpha(fade))
676                        .emissive(spark.color.dimmed(SPARK_EMISSIVE_PEAK))
677                        .additive(),
678                ),
679            );
680        }
681    }
682
683    /// Draws the ball's ghost trail, each ghost smaller and more transparent
684    /// than the one ahead of it; each ghost's position interpolates between
685    /// its own last two resolved ticks by the same `alpha` the ball itself
686    /// draws at, and its radius clamps to what the ball's own radius has
687    /// left over its distance from the head, so a ghost still close to the
688    /// ball never draws past its edge.
689    fn draw_trail(&self, ctx: &mut FrameContext<'_, Breakout>, alpha: f32) {
690        let head = self.ball_trail[1].lerp(self.ball_trail[0], alpha);
691        for i in 0..TRAIL_LEN {
692            let position = self.ball_trail[i + 1].lerp(self.ball_trail[i], alpha);
693            let age = (i + 1) as f32 / TRAIL_LEN as f32;
694            let fade = (1.0 - age).max(TRAIL_ALPHA_FLOOR);
695            let radius = (BALL_RADIUS * TRAIL_SCALE_MIN.lerp(TRAIL_SCALE_MAX, fade))
696                .min((BALL_RADIUS - head.distance(position)).max(0.0));
697            let scale = Vec3::splat(radius * 2.0);
698            ctx.draw(
699                Sphere { subdivisions: 2 }
700                    .at(Transform::from_scale_rotation_translation(
701                        scale,
702                        Quat::IDENTITY,
703                        position,
704                    ))
705                    .material(
706                        Material::color(BALL_GLOW.with_alpha(fade))
707                            .emissive(BALL_EMISSIVE.dimmed(TRAIL_EMISSIVE_PEAK)),
708                    ),
709            );
710        }
711    }
More examples
Hide additional examples
examples/sprite-adventure.rs (line 1694)
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    }
examples/sound-lab.rs (line 555)
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    }

Trait Implementations§

Source§

impl Clone for Color

Source§

fn clone(&self) -> Self

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 Color

Source§

impl Debug for Color

Source§

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

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

impl Default for Color

Source§

fn default() -> Self

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

impl PartialEq for Color

Source§

fn eq(&self, other: &Self) -> 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 Color

Source§

impl StructuralPartialEq for Color

Source§

impl Zeroable for Color

Source§

fn zeroed() -> Self

Auto Trait Implementations§

§

impl Freeze for Color

§

impl RefUnwindSafe for Color

§

impl Send for Color

§

impl Sync for Color

§

impl Unpin for Color

§

impl UnsafeUnpin for Color

§

impl UnwindSafe for Color

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> 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> 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, 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