Skip to main content

lines/
lines.rs

1//! Create a custom material to draw basic lines in 3D
2
3use bevy::{
4    asset::RenderAssetUsages,
5    mesh::{Indices, PrimitiveTopology},
6    prelude::*,
7    reflect::TypePath,
8    render::render_resource::AsBindGroup,
9    shader::ShaderRef,
10};
11
12/// This example uses a shader source file from the assets subdirectory
13const SHADER_ASSET_PATH: &str = "shaders/line_material.wgsl";
14
15fn main() {
16    App::new()
17        .add_plugins((DefaultPlugins, MaterialPlugin::<LineMaterial>::default()))
18        .add_systems(Startup, setup)
19        .run();
20}
21
22fn setup(
23    mut commands: Commands,
24    mut meshes: ResMut<Assets<Mesh>>,
25    mut materials: ResMut<Assets<LineMaterial>>,
26) {
27    // Spawn a list of lines with start and end points for each lines
28    commands.spawn((
29        Mesh3d(meshes.add(LineList {
30            lines: vec![
31                (Vec3::ZERO, Vec3::new(1.0, 1.0, 0.0)),
32                (Vec3::new(1.0, 1.0, 0.0), Vec3::new(1.0, 0.0, 0.0)),
33            ],
34        })),
35        MeshMaterial3d(materials.add(LineMaterial {
36            color: LinearRgba::GREEN,
37        })),
38        Transform::from_xyz(-1.5, 0.0, 0.0),
39    ));
40
41    // Spawn a line strip that goes from point to point
42    commands.spawn((
43        Mesh3d(meshes.add(LineStrip {
44            points: vec![
45                Vec3::ZERO,
46                Vec3::new(1.0, 1.0, 0.0),
47                Vec3::new(2.0, 0.0, 0.0),
48                Vec3::new(2.0, 1.0, 0.0),
49                Vec3::new(3.0, 1.0, 0.0),
50            ],
51            indices: Indices::U16(vec![0, 1, u16::MAX /* primitive restart */, 2, 3, 4]),
52        })),
53        MeshMaterial3d(materials.add(LineMaterial {
54            color: LinearRgba::BLUE,
55        })),
56        Transform::from_xyz(0.5, 0.0, 0.0),
57    ));
58
59    // camera
60    commands.spawn((
61        Camera3d::default(),
62        Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
63    ));
64}
65
66#[derive(Asset, TypePath, Default, AsBindGroup, Debug, Clone)]
67struct LineMaterial {
68    #[uniform(0)]
69    color: LinearRgba,
70}
71
72impl Material for LineMaterial {
73    fn fragment_shader() -> ShaderRef {
74        SHADER_ASSET_PATH.into()
75    }
76}
77
78/// A list of lines with a start and end position
79#[derive(Debug, Clone)]
80struct LineList {
81    lines: Vec<(Vec3, Vec3)>,
82}
83
84impl From<LineList> for Mesh {
85    fn from(line: LineList) -> Self {
86        let vertices: Vec<_> = line.lines.into_iter().flat_map(|(a, b)| [a, b]).collect();
87
88        Mesh::new(
89            // This tells wgpu that the positions are list of lines
90            // where every pair is a start and end point
91            PrimitiveTopology::LineList,
92            RenderAssetUsages::RENDER_WORLD,
93        )
94        // Add the vertices positions as an attribute
95        .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, vertices)
96    }
97}
98
99/// A list of points that will have a line drawn between each consecutive points
100#[derive(Debug, Clone)]
101struct LineStrip {
102    points: Vec<Vec3>,
103    indices: Indices,
104}
105
106impl From<LineStrip> for Mesh {
107    fn from(line: LineStrip) -> Self {
108        Mesh::new(
109            // This tells wgpu that the positions are a list of points
110            // where a line will be drawn between each consecutive point
111            PrimitiveTopology::LineStrip,
112            RenderAssetUsages::RENDER_WORLD,
113        )
114        // Add the point positions as an attribute
115        .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, line.points)
116        .with_inserted_indices(line.indices)
117    }
118}