Skip to main content

re_renderer/
point_cloud_builder.rs

1use re_log::{ResultExt as _, debug_assert_eq};
2
3use crate::allocator::DataTextureSource;
4use crate::draw_phases::PickingLayerObjectId;
5use crate::renderer::gpu_data::PositionRadius;
6use crate::renderer::{
7    PointCloudBatchFlags, PointCloudBatchInfo, PointCloudDrawData, PointCloudDrawDataError,
8};
9use crate::{
10    Color32, CpuWriteGpuReadError, DepthOffset, Label, OutlineMaskPreference,
11    PickingLayerInstanceId, RenderContext, Size,
12};
13
14/// Builder for point clouds, making it easy to create [`crate::renderer::PointCloudDrawData`].
15pub struct PointCloudBuilder<'ctx> {
16    pub(crate) ctx: &'ctx RenderContext,
17
18    // Size of `point`/color` must be equal.
19    pub(crate) position_radius_buffer: DataTextureSource<'ctx, PositionRadius>,
20
21    pub(crate) color_buffer: DataTextureSource<'ctx, Color32>,
22    pub(crate) picking_instance_ids_buffer: DataTextureSource<'ctx, PickingLayerInstanceId>,
23
24    pub(crate) batches: Vec<PointCloudBatchInfo>,
25
26    pub(crate) radius_boost_in_ui_points_for_outlines: f32,
27}
28
29impl<'ctx> PointCloudBuilder<'ctx> {
30    pub fn new(ctx: &'ctx RenderContext) -> Self {
31        Self {
32            ctx,
33            position_radius_buffer: DataTextureSource::new(ctx),
34            color_buffer: DataTextureSource::new(ctx),
35            picking_instance_ids_buffer: DataTextureSource::new(ctx),
36            batches: Vec::with_capacity(16),
37            radius_boost_in_ui_points_for_outlines: 0.0,
38        }
39    }
40
41    /// Returns number of points that can be added without reallocation.
42    /// This may be smaller than the requested number if the maximum number of strips is reached.
43    pub fn reserve(
44        &mut self,
45        expected_number_of_additional_points: usize,
46    ) -> Result<usize, CpuWriteGpuReadError> {
47        re_tracing::profile_function_if!(100_000 < expected_number_of_additional_points);
48
49        // We know that the maximum number is independent of datatype, so we can use the same value for all.
50        self.position_radius_buffer
51            .reserve(expected_number_of_additional_points)?;
52        self.color_buffer
53            .reserve(expected_number_of_additional_points)?;
54        self.picking_instance_ids_buffer
55            .reserve(expected_number_of_additional_points)
56    }
57
58    /// Boosts the size of the points by the given amount of ui-points for the purpose of drawing outlines.
59    pub fn radius_boost_in_ui_points_for_outlines(
60        &mut self,
61        radius_boost_in_ui_points_for_outlines: f32,
62    ) {
63        self.radius_boost_in_ui_points_for_outlines = radius_boost_in_ui_points_for_outlines;
64    }
65
66    /// Start of a new batch.
67    #[inline]
68    pub fn batch(&mut self, label: impl Into<Label>) -> PointCloudBatchBuilder<'_, 'ctx> {
69        self.batches.push(PointCloudBatchInfo {
70            label: label.into(),
71            ..PointCloudBatchInfo::default()
72        });
73
74        PointCloudBatchBuilder(self)
75    }
76
77    #[inline]
78    pub fn batch_with_info(
79        &mut self,
80        info: PointCloudBatchInfo,
81    ) -> PointCloudBatchBuilder<'_, 'ctx> {
82        self.batches.push(info);
83
84        PointCloudBatchBuilder(self)
85    }
86
87    /// Finalizes the builder and returns a point cloud draw data with all the points added so far.
88    pub fn into_draw_data(self) -> Result<PointCloudDrawData, PointCloudDrawDataError> {
89        PointCloudDrawData::new(self)
90    }
91}
92
93pub struct PointCloudBatchBuilder<'a, 'ctx>(&'a mut PointCloudBuilder<'ctx>);
94
95impl Drop for PointCloudBatchBuilder<'_, '_> {
96    fn drop(&mut self) {
97        // Remove batch again if it wasn't actually used.
98        if self.0.batches.last().unwrap().point_count == 0 {
99            self.0.batches.pop();
100        }
101    }
102}
103
104impl PointCloudBatchBuilder<'_, '_> {
105    #[inline]
106    fn batch_mut(&mut self) -> &mut PointCloudBatchInfo {
107        self.0
108            .batches
109            .last_mut()
110            .expect("batch should have been added on PointCloudBatchBuilder creation")
111    }
112
113    /// Sets the `world_from_obj` matrix for the *entire* batch.
114    #[inline]
115    pub fn world_from_obj(mut self, world_from_obj: glam::Affine3A) -> Self {
116        self.batch_mut().world_from_obj = world_from_obj;
117        self
118    }
119
120    /// Sets an outline mask for every element in the batch.
121    #[inline]
122    pub fn outline_mask_ids(mut self, outline_mask_ids: OutlineMaskPreference) -> Self {
123        self.batch_mut().overall_outline_mask_ids = outline_mask_ids;
124        self
125    }
126
127    /// Sets the depth offset for the entire batch.
128    #[inline]
129    pub fn depth_offset(mut self, depth_offset: DepthOffset) -> Self {
130        self.batch_mut().depth_offset = depth_offset;
131        self
132    }
133
134    /// Add several 3D points.
135    ///
136    /// If possible, prefer to using [`Self::add_points`] instead,
137    /// which avoids doing any extra allocations.
138    ///
139    /// Returns a `PointBuilder` which can be used to set the colors, radii, and user-data for the points.
140    ///
141    /// Will add all positions.
142    /// Missing radii will default to `Size::AUTO`.
143    /// Missing colors will default to white.
144    #[inline]
145    pub fn add_points_slow(
146        self,
147        positions: &[glam::Vec3],
148        radii: &[Size],
149        colors: &[Color32],
150        picking_ids: &[PickingLayerInstanceId],
151    ) -> Self {
152        re_tracing::profile_function!();
153
154        let positions_and_radii = PositionRadius::from_many(positions, radii);
155        self.add_points(&positions_and_radii, colors, picking_ids)
156    }
157
158    /// Add several 3D points
159    ///
160    /// Returns a `PointBuilder` which can be used to set the colors, radii, and user-data for the points.
161    ///
162    /// Will add all positions.
163    /// Missing colors will default to white.
164    #[inline]
165    pub fn add_points(
166        mut self,
167        positions_and_radii: &[PositionRadius],
168        colors: &[Color32],
169        picking_ids: &[PickingLayerInstanceId],
170    ) -> Self {
171        re_tracing::profile_function!();
172
173        debug_assert_eq!(
174            self.0.position_radius_buffer.len(),
175            self.0.color_buffer.len()
176        );
177        debug_assert_eq!(
178            self.0.position_radius_buffer.len(),
179            self.0.picking_instance_ids_buffer.len()
180        );
181
182        let num_points = positions_and_radii.len();
183
184        // Do a reserve ahead of time, to check whether we're hitting the data texture limit.
185        // The limit is the same for all data textures, so we only need to check one.
186        let Some(num_available_points) = self
187            .0
188            .position_radius_buffer
189            .reserve(num_points)
190            .ok_or_log_error()
191        else {
192            return self;
193        };
194
195        let num_points = if num_points > num_available_points {
196            re_log::error_once!(
197                "Reached maximum number of points for point cloud of {}. Ignoring all excess points.",
198                self.0.position_radius_buffer.len() + num_available_points
199            );
200            num_available_points
201        } else {
202            num_points
203        };
204
205        if num_points == 0 {
206            return self;
207        }
208
209        // Shorten slices if needed:
210        let positions_and_radii =
211            &positions_and_radii[0..num_points.min(positions_and_radii.len())];
212        let colors = &colors[0..num_points.min(colors.len())];
213        let picking_ids = &picking_ids[0..num_points.min(picking_ids.len())];
214
215        self.batch_mut().point_count += num_points as u32;
216
217        {
218            re_tracing::profile_scope!("positions_and_radii");
219            self.0
220                .position_radius_buffer
221                .extend_from_slice(positions_and_radii)
222                .ok_or_log_error();
223        }
224        {
225            re_tracing::profile_scope!("colors");
226
227            self.0
228                .color_buffer
229                .extend_from_slice(colors)
230                .ok_or_log_error();
231            self.0
232                .color_buffer
233                .add_n(Color32::WHITE, num_points.saturating_sub(colors.len())) // TODO(emilk): don't use a hard-coded default color here
234                .ok_or_log_error();
235        }
236        {
237            re_tracing::profile_scope!("picking_ids");
238
239            self.0
240                .picking_instance_ids_buffer
241                .extend_from_slice(picking_ids)
242                .ok_or_log_error();
243            self.0
244                .picking_instance_ids_buffer
245                .add_n(
246                    PickingLayerInstanceId::default(),
247                    num_points.saturating_sub(picking_ids.len()),
248                )
249                .ok_or_log_error();
250        }
251
252        self
253    }
254
255    /// Adds several 2D points (assumes Z=0). Uses an autogenerated depth value, the same for all points passed.
256    ///
257    /// Will add all positions.
258    /// Missing radii will default to `Size::AUTO`.
259    /// Missing colors will default to white.
260    #[inline]
261    pub fn add_points_2d(
262        self,
263        positions: &[glam::Vec3],
264        radii: &[Size],
265        colors: &[Color32],
266        picking_ids: &[PickingLayerInstanceId],
267    ) -> Self {
268        re_tracing::profile_function!();
269        self.add_points_slow(positions, radii, colors, picking_ids)
270            .flags(PointCloudBatchFlags::FLAG_DRAW_AS_CIRCLES)
271    }
272
273    /// Adds (!) flags for this batch.
274    #[inline]
275    pub fn flags(mut self, flags: PointCloudBatchFlags) -> Self {
276        self.batch_mut().flags |= flags;
277        self
278    }
279
280    /// Sets the picking object id for the current batch.
281    #[inline]
282    pub fn picking_object_id(mut self, picking_object_id: PickingLayerObjectId) -> Self {
283        self.batch_mut().picking_object_id = picking_object_id;
284        self
285    }
286
287    /// Pushes additional outline mask ids for a specific range of points.
288    /// The range is relative to this batch.
289    ///
290    /// Prefer the `overall_outline_mask_ids` setting to set the outline mask ids for the entire batch whenever possible!
291    #[inline]
292    pub fn push_additional_outline_mask_ids_for_range(
293        mut self,
294        range: std::ops::Range<u32>,
295        ids: OutlineMaskPreference,
296    ) -> Self {
297        self.batch_mut()
298            .additional_outline_mask_ids_vertex_ranges
299            .push((range, ids));
300        self
301    }
302}