Skip to main content

vec3

Function vec3 

Source
pub const fn vec3(x: f32, y: f32, z: f32) -> Vec3
Expand description

Rotation and 3D vector (re-exported from glam); also used by the replication helpers (Pose, Interest), which need no 3d feature. Creates a 3-dimensional vector.

Examples found in repository?
examples/ribbons.rs (line 10)
7fn fly(ctx: &mut Ctx) {
8    let t = ctx.time() as f32;
9    ctx.world.query::<(&mut Transform3D, &Orbiting)>().for_each(|(at, _)| {
10        at.position = vec3(t.cos() * 3.0, 1.0 + (t * 2.0).sin() * 0.5, t.sin() * 3.0);
11    });
12}
13
14fn setup(ctx: &mut Ctx) {
15    ctx.camera3d.position = vec3(0.0, 4.0, 9.0);
16    ctx.camera3d.look_at(vec3(0.0, 0.5, 0.0));
17    let floor_material = ctx.create_material(MaterialDesc::color(Color::hex(0x6B7B58)));
18    let floor_data = MeshData::plane(12.0);
19    let floor = ctx.create_mesh(&floor_data, floor_material);
20    ctx.world.spawn((Transform3D::default(), Model::new(floor)));
21
22    // ANCHOR: trail
23    // A glowing tracer behind a flying ball: a quarter second long, thin at
24    // the end and fading out.
25    let mut tracer = Trail3D::new(0.25, 0.12);
26    tracer.color = ParticleGradient::new(vec![
27        (0.0, Color::rgb(1.0, 0.8, 0.3)),
28        (1.0, Color::rgba(1.0, 0.3, 0.0, 0.0)),
29    ]);
30    tracer.blend = ParticleBlend3D::Additive;
31    let white = ctx.default_material();
32    let ball = ctx.create_mesh(&MeshData::sphere(0.15, 16), white);
33    ctx.world
34        .spawn((Transform3D::default(), Model::new(ball), tracer, Orbiting));
35    // After a teleport: ctx.world.get_mut::<Trail3D>(entity).unwrap().clear();
36    // ANCHOR_END: trail
37
38    // ANCHOR: line
39    // A laser beam 5 m long, from the entity forward.
40    let beam = Line3D {
41        blend: ParticleBlend3D::Additive,
42        ..Line3D::new(vec![Vec3::ZERO, vec3(0.0, 0.0, -5.0)], 0.03, Color::RED)
43    };
44    ctx.world.spawn((Transform3D::at(vec3(-4.0, 1.5, 3.0)), beam));
45    // ANCHOR_END: line
46
47    // ANCHOR: decal
48    // A 1 m scorch mark on the floor at (1, 0, -1): the box is 1 x 1 m wide
49    // and 0.4 m deep, its +Z turned up, so the image is projected down.
50    let up = Quat::from_rotation_x(-std::f32::consts::FRAC_PI_2);
51    let projector = Mat4::from_scale_rotation_translation(vec3(1.0, 1.0, 0.4), up, vec3(1.0, 0.0, -1.0));
52    let mark = floor_data.decal(projector, 0.003);
53    if !mark.indices.is_empty() {
54        let scorch = ctx.create_material(MaterialDesc {
55            blend: Blend::Transparent,
56            ..MaterialDesc::color(Color::rgba(0.05, 0.04, 0.03, 0.8))
57        });
58        let mark = ctx.create_mesh(&mark, scorch);
59        // Drawn where the floor is.
60        ctx.world.spawn((Transform3D::default(), Model::new(mark)));
61    }
62    // A mesh from a glTF file works the same: ctx.load_mesh_data("models/wall.glb")?.decal(..)
63    // ANCHOR_END: decal
64}
More examples
Hide additional examples
examples/navmesh3d.rs (line 7)
5fn navigation() -> NavMesh3D {
6    let floor = NavSource::from_mesh(&MeshData::plane(30.0), Transform3D::default());
7    let wall = NavSource::from_mesh(&MeshData::cube(4.0), Transform3D::at(vec3(0.0, 2.0, 0.0)));
8    NavMesh3D::bake(&[floor, wall], NavBakeSettings::default()).unwrap()
9}
10// ANCHOR_END: bake
11
12fn main() {
13    // ANCHOR: query
14    let nav = navigation();
15    let start = nav.sample_position(vec3(-8.0, 1.0, 0.0), 2.0).unwrap();
16    let destination = vec3(8.0, 0.0, 0.0);
17    let corners = nav.find_path(start, destination).unwrap();
18    assert!(corners.len() >= 3);
19    // ANCHOR_END: query
20
21    // ANCHOR: agents
22    let mut world = Game::new("Navigation server", 1, 1)
23        .fixed_hz(60)
24        .sim_system(step_navmesh3d)
25        .run_headless(600, |ctx| {
26            ctx.world.insert_resource(nav);
27            let mut agent = NavAgent3D::default();
28            agent.destination = Some(destination);
29            ctx.world.spawn((Transform3D::at(start), agent));
30            // World-space AABB; move its center or despawn it to reopen polygons.
31            ctx.world
32                .spawn((NavObstacle3D::new(vec3(0.0, 1.0, 8.0), vec3(1.0, 1.0, 1.0)),));
33        });
34    let position = world.query::<&Transform3D>().iter().next().unwrap().position;
35    assert!(position.distance(destination) < 0.3);
36    // ANCHOR_END: agents
37}
examples/scene_viewer.rs (line 36)
29fn camera_from_env() -> Option<(Vec3, f32, f32)> {
30    let v: Vec<f32> = std::env::var("CAMERA")
31        .ok()?
32        .split(',')
33        .map(|n| n.trim().parse().ok())
34        .collect::<Option<_>>()?;
35    let [x, y, z, yaw, pitch] = v[..] else { return None };
36    Some((vec3(x, y, z), yaw.to_radians(), pitch.to_radians()))
37}
38
39fn main() {
40    let scene = std::env::var("SCENE").unwrap_or_else(|_| {
41        eprintln!("say which scene: SCENE=town/Town.json (a file under assets/)");
42        std::process::exit(2);
43    });
44    if std::env::var("HEADLESS").is_ok() {
45        Game::new("scene viewer", 1, 1).run_headless(0, |ctx| {
46            let start = Instant::now();
47            ctx.load_scene(&scene).unwrap_or_else(|e| panic!("{e}"));
48            let models = ctx.world.query::<&Model>().iter().count();
49            let (middle, size) = extent(&mut ctx.world);
50            println!(
51                "{scene}: {models} models in {:.2} s, around {middle} across {size:.0} m",
52                start.elapsed().as_secs_f32()
53            );
54        });
55        return;
56    }
57    let shot = std::env::var("SHOT").ok();
58    let (mut yaw, mut pitch) = (0.0f32, -0.3f32);
59    Game::new("Crabby Engine: scene viewer", 1280, 720)
60        .system(move |ctx: &mut Ctx| {
61            if ctx.key_pressed(Key::Escape) {
62                ctx.quit();
63            }
64            let dt = ctx.dt();
65            if ctx.frame() == 1 {
66                let (y, x, _) = ctx.camera3d.rotation.to_euler(glam::EulerRot::YXZ);
67                (yaw, pitch) = (y, x);
68            }
69            let turn = 1.2 * dt;
70            if ctx.key_down(Key::Left) {
71                yaw += turn;
72            }
73            if ctx.key_down(Key::Right) {
74                yaw -= turn;
75            }
76            if ctx.key_down(Key::Up) {
77                pitch = (pitch + turn).min(1.5);
78            }
79            if ctx.key_down(Key::Down) {
80                pitch = (pitch - turn).max(-1.5);
81            }
82            let rotation = Quat::from_rotation_y(yaw) * Quat::from_rotation_x(pitch);
83            let speed = if ctx.key_down(Key::LeftShift) { 120.0 } else { 20.0 } * dt;
84            let mut go = Vec3::ZERO;
85            for (key, way) in [
86                (Key::W, Vec3::NEG_Z),
87                (Key::S, Vec3::Z),
88                (Key::A, Vec3::NEG_X),
89                (Key::D, Vec3::X),
90            ] {
91                if ctx.key_down(key) {
92                    go += rotation * way;
93                }
94            }
95            if ctx.key_down(Key::E) {
96                go += Vec3::Y;
97            }
98            if ctx.key_down(Key::Q) {
99                go -= Vec3::Y;
100            }
101            ctx.camera3d.position += go * speed;
102            ctx.camera3d.rotation = rotation;
103            if ctx.frame() == 300
104                && let Some(path) = &shot
105            {
106                ctx.save_screenshot(path);
107            }
108            if ctx.frame() == 302 && shot.is_some() {
109                ctx.quit();
110            }
111        })
112        .run(move |ctx| {
113            ctx.load_scene(&scene).unwrap_or_else(|e| panic!("{e}"));
114            ctx.lighting.sun_direction = Sky::sun_direction_at(15.0);
115            ctx.lighting.shadow_distance = 400.0;
116            ctx.camera3d.far = 3000.0;
117            ctx.post.bloom = 0.05;
118            let (middle, size) = extent(&mut ctx.world);
119            let (at, yaw, pitch) =
120                camera_from_env().unwrap_or((middle + vec3(0.0, size * 0.25, size * 0.4), 0.0, -0.5));
121            ctx.camera3d.position = at;
122            ctx.camera3d.rotation = Quat::from_rotation_y(yaw) * Quat::from_rotation_x(pitch);
123        });
124}
examples/shaders.rs (line 17)
14fn blade() -> MeshData {
15    MeshData {
16        positions: vec![
17            vec3(-0.03, 0.0, 0.0),
18            vec3(0.03, 0.0, 0.0),
19            vec3(0.01, 0.6, 0.0),
20            vec3(-0.01, 0.6, 0.0),
21        ],
22        normals: vec![Vec3::Z; 4],
23        uvs: vec![vec2(0.0, 0.0), vec2(1.0, 0.0), vec2(1.0, 1.0), vec2(0.0, 1.0)],
24        indices: vec![0, 1, 2, 0, 2, 3],
25        ..Default::default()
26    }
27}
28
29/// A pattern made in the shader: no texture needed.
30const TILES: &str = "
31fn surface(s: Surface) -> Surface {
32    var out = s;
33    let cell = floor(s.uv * vec2<f32>(16.0, 8.0));
34    let checker = (cell.x + cell.y) % 2.0;
35    out.albedo = mix(vec3<f32>(0.8, 0.3, 0.1), vec3<f32>(0.95, 0.85, 0.6), checker);
36    out.roughness = mix(0.25, 0.7, checker);
37    return out;
38}";
39
40fn main() {
41    let env = |name| std::env::var(name).ok().and_then(|v| v.parse::<f32>().ok());
42    let mut hour = env("HOUR").unwrap_or(15.5);
43    let mut fog = env("FOG").unwrap_or(0.0) > 0.0;
44    Game::new("Crabby Engine: shaders", 1280, 720)
45        .system(move |ctx: &mut Ctx| {
46            if ctx.key_pressed(Key::Escape) {
47                ctx.quit();
48            }
49            // Two hours a second.
50            if ctx.key_down(Key::Right) {
51                hour = (hour + ctx.dt() * 2.0) % 24.0;
52            }
53            if ctx.key_down(Key::Left) {
54                hour = (hour - ctx.dt() * 2.0).rem_euclid(24.0);
55            }
56            if ctx.key_pressed(Key::F) {
57                fog = !fog;
58            }
59            ctx.lighting.sun_direction = Sky::sun_direction_at(hour);
60            ctx.sky.fog_density = if fog { 0.02 } else { 0.0 };
61            ctx.sky.fog_falloff = 8.0;
62            if ctx.frame() == 2
63                && let Ok(path) = std::env::var("SHOT")
64            {
65                ctx.save_screenshot(path);
66            }
67            if ctx.frame() == 4 && std::env::var("SHOT").is_ok() {
68                ctx.quit();
69            }
70        })
71        .run(|ctx| {
72            ctx.clear(Color::hex(0x9CC4E4));
73            ctx.camera3d.position = vec3(0.0, 4.0, 11.0);
74            ctx.camera3d.look_at(vec3(0.0, 0.5, 0.0));
75            ctx.post.bloom = 0.1;
76
77            let ground_look = MaterialDesc {
78                roughness: 0.95,
79                ..MaterialDesc::color(Color::hex(0x5E7A3A))
80            };
81            let ground = ctx.create_material(ground_look);
82            let ground = ctx.create_mesh(&MeshData::plane(600.0), ground);
83            ctx.world.spawn((Transform3D::default(), Model::new(ground)));
84
85            let water = ctx.load_shader("shaders/water.wgsl");
86            let lake = ctx.create_material(MaterialDesc {
87                shader: Some(water),
88                blend: Blend::Transparent,
89                custom: [vec4(0.06, 1.5, 1.2, 1.2), Vec4::ZERO, Vec4::ZERO, Vec4::ZERO],
90                ..MaterialDesc::color(Color::hex(0x1E4D6B))
91            });
92            let mut surface = MeshData::plane(12.0);
93            // More vertices, so the waves have something to move.
94            surface = subdivide(surface, 5);
95            let lake = ctx.create_mesh(&surface, lake);
96            ctx.world
97                .spawn((Transform3D::at(vec3(-3.0, 0.15, -4.0)), Model::new(lake)));
98
99            // The lake bed, under the water: sand sloping down, and stones.
100            let sand = ctx.create_material(MaterialDesc {
101                roughness: 0.9,
102                ..MaterialDesc::color(Color::hex(0xC2A878))
103            });
104            let bed = ctx.create_mesh(&MeshData::plane(12.0), sand);
105            let tilt = Quat::from_rotation_x(0.12);
106            ctx.world.spawn((
107                Transform3D {
108                    rotation: tilt,
109                    ..Transform3D::at(vec3(-3.0, -0.6, -4.0))
110                },
111                Model::new(bed),
112            ));
113            let stone = ctx.create_material(MaterialDesc {
114                roughness: 0.7,
115                ..MaterialDesc::color(Color::hex(0x8A8A80))
116            });
117            let pebble = ctx.create_mesh(&MeshData::sphere(0.35, 16), stone);
118            for i in 0..12 {
119                let at = vec3(
120                    -7.0 + i as f32 * 0.8,
121                    -0.3 - (i % 3) as f32 * 0.2,
122                    -3.0 + (i % 4) as f32 * -1.5,
123                );
124                ctx.world.spawn((Transform3D::at(at), Model::new(pebble)));
125            }
126
127            let wind = ctx.load_shader("shaders/wind.wgsl");
128            let grass = ctx.create_material(MaterialDesc {
129                shader: Some(wind),
130                double_sided: true,
131                roughness: 0.8,
132                custom: [vec4(0.25, 2.0, 0.0, 0.0), Vec4::ZERO, Vec4::ZERO, Vec4::ZERO],
133                ..MaterialDesc::color(Color::hex(0x7FA83C))
134            });
135            let blade = ctx.create_mesh(&blade(), grass);
136            let mut seed = 7u32;
137            let mut random = move || {
138                seed = seed.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
139                (seed >> 8) as f32 / (1 << 24) as f32
140            };
141            for _ in 0..6000 {
142                let at = vec3(random() * 14.0 - 2.0, 0.0, random() * 10.0 - 3.0);
143                if at.x < 4.0 && at.z < 2.5 {
144                    continue; // the lake
145                }
146                let t = Transform3D {
147                    rotation: Quat::from_rotation_y(random() * 6.3),
148                    scale: Vec3::splat(0.6 + random() * 0.8),
149                    ..Transform3D::at(at)
150                };
151                ctx.world.spawn((t, Model::new(blade)));
152            }
153
154            let tiles = ctx.create_shader("tiles", TILES);
155            let ball = ctx.create_material(MaterialDesc {
156                shader: Some(tiles),
157                ..Default::default()
158            });
159            let ball = ctx.create_mesh(&MeshData::sphere(1.0, 48), ball);
160            ctx.world
161                .spawn((Transform3D::at(vec3(3.5, 1.0, 1.0)), Model::new(ball)));
162        });
163}
examples/vehicle.rs (line 44)
41fn spawn_car(ctx: &mut Ctx, at: Vec3) -> Entity {
42    let mut car = Vehicle3D::new();
43    for (x, z) in [(-0.8, -1.3), (0.8, -1.3), (-0.8, 1.3), (0.8, 1.3)] {
44        let mut wheel = Wheel3D::new(vec3(x, -0.3, z), 0.35);
45        wheel.sideways_friction.stiffness = 1.3;
46        car = car.with_wheel(wheel);
47    }
48    ctx.world.spawn((
49        Transform3D::at(at),
50        RigidBody3D::dynamic()
51            .mass(1200.0)
52            .center_of_mass(vec3(0.0, -0.35, 0.0)),
53        Collider3D::cuboid(vec3(1.8, 0.6, 4.2)),
54        Velocity3D::default(),
55        car,
56    ))
57}
58// ANCHOR_END: car
59
60// ANCHOR: drive
61/// The game's half of driving, before each physics step: rear-wheel
62/// drive, brakes on all four, steering that eases in and tightens less at
63/// speed.
64fn drive(ctx: &mut Ctx) {
65    let controls = *ctx.world.resource::<Controls>();
66    let dt = ctx.dt();
67    let car = ctx.world.resource::<Player>().car;
68    let forward = forward_speed(&ctx.world, car);
69    let wanted = controls.steer * 0.6 / (1.0 + forward.abs() * 0.08);
70    let player = ctx.world.resource_mut::<Player>();
71    player.steer += (wanted - player.steer).clamp(-2.0 * dt, 2.0 * dt);
72    let steer = player.steer;
73    let Some(vehicle) = ctx.world.get_mut::<Vehicle3D>(car) else {
74        return;
75    };
76    // Pressing against the way it rolls brakes; from (almost) still it drives.
77    let braking = controls.throttle * forward < 0.0 && forward.abs() > 0.5;
78    for (i, wheel) in vehicle.wheels_mut().iter_mut().enumerate() {
79        let front = i < 2;
80        wheel.steer_angle = if front { steer } else { 0.0 };
81        wheel.motor_torque = if front || braking {
82            0.0
83        } else {
84            controls.throttle * 900.0
85        };
86        wheel.brake_torque = if braking {
87            3000.0
88        } else if controls.handbrake && !front {
89            5000.0
90        } else {
91            0.0
92        };
93    }
94}
95// ANCHOR_END: drive
96
97fn speed_of(world: &World, car: Entity) -> f32 {
98    world.get::<Velocity3D>(car).map_or(0.0, |v| v.linear.length())
99}
100
101/// Speed along where the car faces (negative backing up).
102fn forward_speed(world: &World, car: Entity) -> f32 {
103    match (world.get::<Velocity3D>(car), world.get::<Transform3D>(car)) {
104        (Some(v), Some(t)) => v.linear.dot(t.forward()),
105        _ => 0.0,
106    }
107}
108
109/// Keys to controls (windowed), in the same tick as `drive`.
110fn keys(ctx: &mut Ctx) {
111    let axis = |plus: bool, minus: bool| plus as i32 as f32 - minus as i32 as f32;
112    let controls = Controls {
113        throttle: axis(ctx.key_down(Key::W), ctx.key_down(Key::S)),
114        steer: axis(ctx.key_down(Key::D), ctx.key_down(Key::A)),
115        handbrake: ctx.key_down(Key::Space),
116    };
117    *ctx.world.resource_mut::<Controls>() = controls;
118    if ctx.key_pressed(Key::R) {
119        let car = ctx.world.resource::<Player>().car;
120        if let Some(t) = ctx.world.get_mut::<Transform3D>(car) {
121            let p = t.position;
122            *t = Transform3D::at(vec3(p.x, height(p.x, p.z) + 1.5, p.z));
123        }
124    }
125}
126
127// ANCHOR: wheels
128/// Every frame: each wheel drawn where its car's last step put it (steer,
129/// spin and suspension travel), following the chassis's smoothed pose.
130fn place_wheels(ctx: &mut Ctx) {
131    let player = ctx.world.resource::<Player>();
132    let car = player.car;
133    let (Some(&vehicle), Some(&chassis)) = (ctx.world.get::<Vehicle3D>(car), ctx.world.get::<Transform3D>(car)) else {
134        return;
135    };
136    for i in 0..player.wheels.len() {
137        let e = ctx.world.resource::<Player>().wheels[i];
138        let pose = vehicle.wheels()[i].pose(&chassis);
139        if let Some(t) = ctx.world.get_mut::<Transform3D>(e) {
140            *t = Transform3D { scale: t.scale, ..pose };
141        }
142    }
143}
144// ANCHOR_END: wheels
145
146/// Every frame: the camera trails the car.
147fn chase(ctx: &mut Ctx) {
148    let car = ctx.world.resource::<Player>().car;
149    let Some(t) = ctx.world.get::<Transform3D>(car).copied() else {
150        return;
151    };
152    let back = -t.forward().with_y(0.0).normalize_or(Vec3::Z);
153    let eye = t.position + back * 9.0 + Vec3::Y * 3.5;
154    let k = (ctx.dt() * 4.0).min(1.0);
155    ctx.camera3d.position = ctx.camera3d.position.lerp(eye, k);
156    let look = Transform3D::at(ctx.camera3d.position).looking_at(t.position + Vec3::Y);
157    ctx.camera3d.rotation = look.rotation;
158}
159
160fn build_world(ctx: &mut Ctx, draw: bool) {
161    let corner = vec3(-SIZE / 2.0, 0.0, -SIZE / 2.0);
162    let step = SIZE / (N - 1) as f32;
163    let heights: Vec<f32> = (0..N * N)
164        .map(|i| height(corner.x + (i % N) as f32 * step, corner.z + (i / N) as f32 * step))
165        .collect();
166    let ground = ctx.physics3d.heightfield(&heights, SIZE);
167    let terrain = ctx
168        .world
169        .spawn((Transform3D::at(corner), RigidBody3D::fixed(), Collider3D::mesh(ground)));
170    let car = spawn_car(ctx, vec3(0.0, height(0.0, 0.0) + 1.5, 0.0));
171    ctx.world.insert_resource(Player {
172        car,
173        steer: 0.0,
174        wheels: Vec::new(),
175    });
176    ctx.world.insert_resource(Controls::default());
177    if !draw {
178        return;
179    }
180    let look = grass(ctx);
181    let tile = ctx.create_terrain_tile(&heights, SIZE, look);
182    ctx.world.insert(terrain, tile);
183    let paint = ctx.create_material(MaterialDesc::color(Color::hex(0xC8402E)));
184    let body = ctx.create_mesh(&MeshData::cube(1.0), paint);
185    if let Some(t) = ctx.world.get_mut::<Transform3D>(car) {
186        t.scale = vec3(1.8, 0.6, 4.2);
187    }
188    ctx.world.insert(car, Model::new(body));
189    let rubber = ctx.create_material(MaterialDesc::color(Color::hex(0x202020)));
190    let tyre = ctx.create_mesh(&MeshData::cube(1.0), rubber);
191    for _ in 0..4 {
192        let wheel = ctx.world.spawn((
193            Transform3D {
194                scale: vec3(0.3, 0.7, 0.7),
195                ..Transform3D::default()
196            },
197            Model::new(tyre),
198        ));
199        ctx.world.resource_mut::<Player>().wheels.push(wheel);
200    }
201}
202
203/// A one-layer terrain material: grass everywhere.
204fn grass(ctx: &mut Ctx) -> Material {
205    let green: Vec<u8> = (0..16 * 16)
206        .flat_map(|i| {
207            let shade = 200 + (i * 37 % 56) as u8;
208            [
209                (70 * shade as u32 / 255) as u8,
210                (120 * shade as u32 / 255) as u8,
211                45,
212                255,
213            ]
214        })
215        .collect();
216    let layers = ctx.create_texture_array(16, 16, &[&green], TextureKind::Color);
217    let weights = ctx.create_texture_array(1, 1, &[&[255, 0, 0, 0]], TextureKind::Data);
218    let corner = vec2(-SIZE / 2.0, -SIZE / 2.0);
219    ctx.create_terrain_material(&TerrainMaterialDesc::new(layers, weights, corner, vec2(SIZE, SIZE)))
220}
221
222/// The script for `HEADLESS=1`: full throttle for 4 s, then steer right
223/// for 2 s, then brake to a stop and hold it with the handbrake.
224fn autopilot(ctx: &mut Ctx) {
225    let t = ctx.sim_tick() as f32 * ctx.dt();
226    let car = ctx.world.resource::<Player>().car;
227    let rolling = forward_speed(&ctx.world, car) > 0.3;
228    *ctx.world.resource_mut::<Controls>() = Controls {
229        throttle: if t < 6.0 {
230            1.0
231        } else if rolling {
232            -1.0
233        } else {
234            0.0
235        },
236        steer: if (4.0..6.0).contains(&t) { 1.0 } else { 0.0 },
237        handbrake: t >= 6.0 && !rolling,
238    };
239}
240
241/// What the car did, second by second.
242#[derive(Default)]
243struct Log(Vec<(f32, Vec3)>);
244
245fn record(ctx: &mut Ctx) {
246    if ctx.sim_tick().is_multiple_of(50) {
247        let car = ctx.world.resource::<Player>().car;
248        let speed = speed_of(&ctx.world, car);
249        let facing = ctx.world.get::<Transform3D>(car).map_or(Vec3::NEG_Z, |t| t.forward());
250        ctx.world.resource_mut::<Log>().0.push((speed, facing));
251    }
252}
253
254/// Runs the script and checks what the car did.
255fn headless() {
256    let world = Game::new("vehicle", 1, 1)
257        .sim_system(autopilot)
258        .sim_system(drive)
259        .physics3d(vec3(0.0, -9.81, 0.0))
260        .sim_system(record)
261        .run_headless(500, |ctx| {
262            build_world(ctx, false);
263            ctx.world.insert_resource(Log::default());
264        });
265    let log = &world.resource::<Log>().0;
266    for (second, (speed, facing)) in log.iter().enumerate() {
267        println!("{:2} s: {speed:5.2} m/s, facing {facing:.2}", second + 1);
268    }
269    let (accelerated, _) = log[3];
270    let (_, turned) = log[5];
271    let (stopped, _) = log[log.len() - 1];
272    assert!(accelerated > 8.0, "accelerates: {accelerated} m/s after 4 s");
273    assert!(turned.x > 0.3, "steers right: facing {turned}");
274    assert!(stopped < 0.5, "stops: {stopped} m/s");
275    println!("accelerates, steers and stops");
276}
277
278fn main() {
279    if std::env::var("HEADLESS").is_ok() {
280        headless();
281        return;
282    }
283    // ANCHOR: setup
284    Game::new("Crabby Engine: vehicle", 1280, 720)
285        .sim_system(keys)
286        .sim_system(drive) // sets the wheels' inputs...
287        .physics3d(vec3(0.0, -9.81, 0.0)) // ...for the step that drives them
288        .system(place_wheels)
289        .system(chase)
290        // ANCHOR_END: setup
291        .run(|ctx| {
292            ctx.clear(Color::hex(0x9CC4E4));
293            build_world(ctx, true);
294        });
295}
examples/audio3d.rs (line 22)
5fn setup(ctx: &mut Ctx) {
6    let samples: Vec<f32> = (0..44100)
7        .map(|i| (i as f32 * 220.0 * std::f32::consts::TAU / 44100.0).sin() * 0.1)
8        .collect();
9    let hum = ctx.create_sound(44100, &samples);
10    let settings = AudioSettings3D {
11        sound: SoundParams {
12            volume: 0.4,
13            looping: true,
14            ..Default::default()
15        },
16        min_distance: 2.0,
17        max_distance: 60.0,
18        rolloff: AudioRolloff::Linear,
19        ..Default::default()
20    };
21    ctx.world
22        .spawn((Transform3D::at(vec3(4.0, 0.0, 0.0)), AudioSource3D::new(hum, settings)));
23    ctx.play_sound_at(&hum, vec3(-4.0, 0.0, 0.0), AudioSettings3D::default());
24}
25
26fn main() {
27    // The sounds follow their entities and the listener (ctx.camera3d) by themselves.
28    Game::new("Spatial audio", 800, 600).run(setup);
29}
30// ANCHOR_END: sound3d
31
32// ANCHOR: audio_mixer
33#[allow(dead_code)]
34fn buses_and_effects(ctx: &mut Ctx, sound: Sound) {
35    let music = ctx.audio_bus("music"); // Save this handle; names are looked up once.
36    ctx.set_audio_bus_volume(music, 0.6);
37    let voice = ctx.play_sound_on(
38        &sound,
39        SoundParams {
40            looping: true,
41            ..Default::default()
42        },
43        music,
44        AudioEffects {
45            low_pass_hz: 4_000.0,
46            reverb: 0.2,
47        },
48    );
49    ctx.set_sound_effects(
50        voice,
51        AudioEffects {
52            low_pass_hz: 800.0,
53            reverb: 0.5,
54        },
55    );
56    ctx.mute_audio_bus(music, true);
57    ctx.mute_audio_bus(music, false); // Restores the configured gain.
58    let settings = AudioSettings3D {
59        bus: music,
60        effects: AudioEffects {
61            low_pass_hz: 2_000.0,
62            reverb: 0.3,
63        },
64        ..Default::default()
65    };
66    ctx.play_sound_at(&sound, vec3(3.0, 0.0, 0.0), settings);
67}