1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
//! This example shows how to sample random points from primitive shapes.

use bevy::{
    input::mouse::{MouseButtonInput, MouseMotion},
    math::prelude::*,
    prelude::*,
    render::mesh::SphereKind,
};
use rand::{distributions::Distribution, SeedableRng};
use rand_chacha::ChaCha8Rng;

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_systems(Startup, setup)
        .add_systems(Update, (handle_mouse, handle_keypress))
        .run();
}

/// Resource for the random sampling mode, telling whether to sample the interior or the boundary.
#[derive(Resource)]
enum Mode {
    Interior,
    Boundary,
}

/// Resource storing the shape being sampled.
#[derive(Resource)]
struct SampledShape(Cuboid);

/// The source of randomness used by this example.
#[derive(Resource)]
struct RandomSource(ChaCha8Rng);

/// A container for the handle storing the mesh used to display sampled points as spheres.
#[derive(Resource)]
struct PointMesh(Handle<Mesh>);

/// A container for the handle storing the material used to display sampled points.
#[derive(Resource)]
struct PointMaterial(Handle<StandardMaterial>);

/// Marker component for sampled points.
#[derive(Component)]
struct SamplePoint;

/// The pressed state of the mouse, used for camera motion.
#[derive(Resource)]
struct MousePressed(bool);

fn setup(
    mut commands: Commands,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<StandardMaterial>>,
) {
    // Use seeded rng and store it in a resource; this makes the random output reproducible.
    let seeded_rng = ChaCha8Rng::seed_from_u64(19878367467712);
    commands.insert_resource(RandomSource(seeded_rng));

    // Make a plane for establishing space.
    commands.spawn(PbrBundle {
        mesh: meshes.add(Plane3d::default().mesh().size(12.0, 12.0)),
        material: materials.add(Color::srgb(0.3, 0.5, 0.3)),
        transform: Transform::from_xyz(0.0, -2.5, 0.0),
        ..default()
    });

    // Store the shape we sample from in a resource:
    let shape = Cuboid::from_length(2.9);
    commands.insert_resource(SampledShape(shape));

    // The sampled shape shown transparently:
    commands.spawn(PbrBundle {
        mesh: meshes.add(shape),
        material: materials.add(StandardMaterial {
            base_color: Color::srgba(0.2, 0.1, 0.6, 0.3),
            alpha_mode: AlphaMode::Blend,
            cull_mode: None,
            ..default()
        }),
        ..default()
    });

    // A light:
    commands.spawn(PointLightBundle {
        point_light: PointLight {
            shadows_enabled: true,
            ..default()
        },
        transform: Transform::from_xyz(4.0, 8.0, 4.0),
        ..default()
    });

    // A camera:
    commands.spawn(Camera3dBundle {
        transform: Transform::from_xyz(-2.0, 3.0, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
        ..default()
    });

    // Store the mesh and material for sample points in resources:
    commands.insert_resource(PointMesh(
        meshes.add(
            Sphere::new(0.03)
                .mesh()
                .kind(SphereKind::Ico { subdivisions: 3 }),
        ),
    ));
    commands.insert_resource(PointMaterial(materials.add(StandardMaterial {
        base_color: Color::srgb(1.0, 0.8, 0.8),
        metallic: 0.8,
        ..default()
    })));

    // Instructions for the example:
    commands.spawn(
        TextBundle::from_section(
            "Controls:\n\
            M: Toggle between sampling boundary and interior.\n\
            R: Restart (erase all samples).\n\
            S: Add one random sample.\n\
            D: Add 100 random samples.\n\
            Rotate camera by panning left/right.",
            TextStyle::default(),
        )
        .with_style(Style {
            position_type: PositionType::Absolute,
            top: Val::Px(12.0),
            left: Val::Px(12.0),
            ..default()
        }),
    );

    // The mode starts with interior points.
    commands.insert_resource(Mode::Interior);

    // Starting mouse-pressed state is false.
    commands.insert_resource(MousePressed(false));
}

// Handle user inputs from the keyboard:
#[allow(clippy::too_many_arguments)]
fn handle_keypress(
    mut commands: Commands,
    keyboard: Res<ButtonInput<KeyCode>>,
    mut mode: ResMut<Mode>,
    shape: Res<SampledShape>,
    mut random_source: ResMut<RandomSource>,
    sample_mesh: Res<PointMesh>,
    sample_material: Res<PointMaterial>,
    samples: Query<Entity, With<SamplePoint>>,
) {
    // R => restart, deleting all samples
    if keyboard.just_pressed(KeyCode::KeyR) {
        for entity in &samples {
            commands.entity(entity).despawn();
        }
    }

    // S => sample once
    if keyboard.just_pressed(KeyCode::KeyS) {
        let rng = &mut random_source.0;

        // Get a single random Vec3:
        let sample: Vec3 = match *mode {
            Mode::Interior => shape.0.sample_interior(rng),
            Mode::Boundary => shape.0.sample_boundary(rng),
        };

        // Spawn a sphere at the random location:
        commands.spawn((
            PbrBundle {
                mesh: sample_mesh.0.clone(),
                material: sample_material.0.clone(),
                transform: Transform::from_translation(sample),
                ..default()
            },
            SamplePoint,
        ));

        // NOTE: The point is inside the cube created at setup just because of how the
        // scene is constructed; in general, you would want to use something like
        // `cube_transform.transform_point(sample)` to get the position of where the sample
        // would be after adjusting for the position and orientation of the cube.
        //
        // If the spawned point also needed to follow the position of the cube as it moved,
        // then making it a child entity of the cube would be a good approach.
    }

    // D => generate many samples
    if keyboard.just_pressed(KeyCode::KeyD) {
        let mut rng = &mut random_source.0;

        // Get 100 random Vec3s:
        let samples: Vec<Vec3> = match *mode {
            Mode::Interior => {
                let dist = shape.0.interior_dist();
                dist.sample_iter(&mut rng).take(100).collect()
            }
            Mode::Boundary => {
                let dist = shape.0.boundary_dist();
                dist.sample_iter(&mut rng).take(100).collect()
            }
        };

        // For each sample point, spawn a sphere:
        for sample in samples {
            commands.spawn((
                PbrBundle {
                    mesh: sample_mesh.0.clone(),
                    material: sample_material.0.clone(),
                    transform: Transform::from_translation(sample),
                    ..default()
                },
                SamplePoint,
            ));
        }

        // NOTE: See the previous note above regarding the positioning of these samples
        // relative to the transform of the cube containing them.
    }

    // M => toggle mode between interior and boundary.
    if keyboard.just_pressed(KeyCode::KeyM) {
        match *mode {
            Mode::Interior => *mode = Mode::Boundary,
            Mode::Boundary => *mode = Mode::Interior,
        }
    }
}

// Handle user mouse input for panning the camera around:
fn handle_mouse(
    mut button_events: EventReader<MouseButtonInput>,
    mut motion_events: EventReader<MouseMotion>,
    mut camera: Query<&mut Transform, With<Camera>>,
    mut mouse_pressed: ResMut<MousePressed>,
) {
    // Store left-pressed state in the MousePressed resource
    for button_event in button_events.read() {
        if button_event.button != MouseButton::Left {
            continue;
        }
        *mouse_pressed = MousePressed(button_event.state.is_pressed());
    }

    // If the mouse is not pressed, just ignore motion events
    if !mouse_pressed.0 {
        return;
    }
    let displacement: f32 = motion_events.read().map(|motion| motion.delta.x).sum();
    let mut camera_transform = camera.single_mut();
    camera_transform.rotate_around(Vec3::ZERO, Quat::from_rotation_y(-displacement / 150.));
}