Skip to main content

three_d/renderer/object/
wireframe.rs

1use crate::core::*;
2use crate::renderer::*;
3
4/// Renders a triangle mesh as a screen-space wireframe.
5///
6/// The wireframe is generated from per-vertex barycentric coordinates and drawn
7/// on top of triangle faces in the fragment shader.
8pub struct Wireframe {
9    material: WireframeMaterial,
10    positions: VertexBuffer<Vec3>,
11    barycentric: VertexBuffer<Vec3>,
12    context: Context,
13    aabb: AxisAlignedBoundingBox,
14    transformation: Mat4,
15}
16
17impl Wireframe {
18    /// Creates a new wireframe object from vertex positions
19    pub fn new(context: &Context, positions: &[Vec3], wire_width: f32, wire_color: Srgba) -> Self {
20        let barycentric = VertexBuffer::new_with_data(
21            context,
22            &(0..positions.len() / 3)
23                .flat_map(|_| [vec3(1., 0., 0.), vec3(0., 1., 0.), vec3(0., 0., 1.)])
24                .collect::<Vec<_>>(),
25        );
26        Self {
27            material: material::WireframeMaterial {
28                line_width: wire_width,
29                line_color: wire_color,
30            },
31            positions: VertexBuffer::new_with_data(context, positions),
32            barycentric,
33            context: context.clone(),
34            aabb: AxisAlignedBoundingBox::new_with_positions(positions),
35            transformation: Mat4::identity(),
36        }
37    }
38
39    /// Creates a new wireframe object from a CPU mesh.
40    pub fn new_from_cpu_mesh(
41        context: &Context,
42        cpu_mesh: &CpuMesh,
43        wire_width: f32,
44        wire_color: Srgba,
45    ) -> Self {
46        let positions = if let Some(indices) = cpu_mesh.indices.to_u32() {
47            let source_positions = cpu_mesh.positions.to_f32();
48            indices
49                .into_iter()
50                .map(|index| source_positions[index as usize])
51                .collect()
52        } else {
53            cpu_mesh.positions.to_f32()
54        };
55        Self::new(context, &positions, wire_width, wire_color)
56    }
57
58    /// Creates a new wireframe object from a CPU model.
59    pub fn new_from_cpu_model(
60        context: &Context,
61        cpu_model: &CpuModel,
62        wire_width: f32,
63        wire_color: Srgba,
64    ) -> Vec<Self> {
65        cpu_model
66            .geometries
67            .iter()
68            .filter_map(|g| match &g.geometry {
69                three_d_asset::Geometry::Triangles(mesh) => {
70                    let mut wireframe =
71                        Wireframe::new_from_cpu_mesh(context, mesh, wire_width, wire_color);
72                    wireframe.set_transformation(g.transformation);
73                    Some(wireframe)
74                }
75                _ => None,
76            })
77            .collect::<Vec<_>>()
78    }
79
80    /// Sets the model transformation applied when rendering.
81    pub fn set_transformation(&mut self, transformation: Mat4) {
82        self.transformation = transformation
83    }
84
85    /// Sets the wire thickness.
86    pub fn set_wire_width(&mut self, width: f32) {
87        self.material.line_width = width;
88    }
89
90    /// Sets the wire color.
91    pub fn set_wire_color(&mut self, color: Srgba) {
92        self.material.line_color = color;
93    }
94}
95
96impl<'a> IntoIterator for &'a Wireframe {
97    type Item = &'a dyn Object;
98    type IntoIter = std::iter::Once<&'a dyn Object>;
99
100    fn into_iter(self) -> Self::IntoIter {
101        std::iter::once(self)
102    }
103}
104
105impl Object for Wireframe {
106    fn render(&self, viewer: &dyn Viewer, lights: &[&dyn Light]) {
107        if let Err(e) = render_with_material(&self.context, viewer, self, &self.material, lights) {
108            panic!("{}", e.to_string());
109        }
110    }
111
112    fn material_type(&self) -> MaterialType {
113        self.material.material_type()
114    }
115}
116
117impl Geometry for Wireframe {
118    fn draw(&self, viewer: &dyn Viewer, program: &Program, render_states: RenderStates) {
119        program.use_uniform("viewProjection", viewer.projection() * viewer.view());
120        program.use_uniform("modelMatrix", self.transformation);
121        program.use_vertex_attribute("position", &self.positions);
122        program.use_vertex_attribute("barycentric", &self.barycentric);
123        program.draw_arrays(
124            render_states,
125            viewer.viewport(),
126            self.positions.vertex_count(),
127        )
128    }
129
130    fn vertex_shader_source(&self) -> String {
131        include_str!("shaders/wireframe.vert").to_owned()
132    }
133
134    fn id(&self) -> GeometryId {
135        GeometryId::Wireframe
136    }
137
138    fn render_with_material(
139        &self,
140        material: &dyn Material,
141        viewer: &dyn Viewer,
142        lights: &[&dyn Light],
143    ) {
144        if let Err(e) = render_with_material(&self.context, viewer, &self, material, lights) {
145            panic!("{}", e.to_string());
146        }
147    }
148
149    fn render_with_effect(
150        &self,
151        material: &dyn Effect,
152        viewer: &dyn Viewer,
153        lights: &[&dyn Light],
154        color_texture: Option<ColorTexture>,
155        depth_texture: Option<DepthTexture>,
156    ) {
157        if let Err(e) = render_with_effect(
158            &self.context,
159            viewer,
160            self,
161            material,
162            lights,
163            color_texture,
164            depth_texture,
165        ) {
166            panic!("{}", e.to_string());
167        }
168    }
169
170    fn aabb(&self) -> AxisAlignedBoundingBox {
171        self.aabb.transformed(self.transformation)
172    }
173}