Skip to main content

ribbons/
ribbons.rs

1//! Ribbon trails, lines and decals: `cargo run --features 3d --example ribbons`.
2use ponko::*;
3
4/// Flies in a circle.
5struct Orbiting;
6
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}
65
66fn main() {
67    Game::new("Ribbons", 960, 640).system(fly).run(setup);
68}