Skip to main content

dear_implot3d/
surface_builder.rs

1use std::borrow::Cow;
2
3use crate::item_style::{Plot3DItemStyle, plot3d_spec_with_style};
4use crate::{
5    Item3DFlags, Plot3DDataLayout, Plot3DUi, Surface3DFlags, debug_before_plot, plot3d_spec_from,
6    surface_count_to_i32, sys,
7};
8
9/// Surface (grid) plot builder (f32 variant)
10pub struct Surface3DBuilder<'ui> {
11    pub(crate) _ui: &'ui Plot3DUi<'ui>,
12    pub(crate) label: Cow<'ui, str>,
13    pub(crate) xs: &'ui [f32],
14    pub(crate) ys: &'ui [f32],
15    pub(crate) zs: &'ui [f32],
16    pub(crate) scale_min: f64,
17    pub(crate) scale_max: f64,
18    pub(crate) flags: Surface3DFlags,
19    pub(crate) item_flags: Item3DFlags,
20    pub(crate) style: Plot3DItemStyle,
21}
22
23impl<'ui> Surface3DBuilder<'ui> {
24    pub fn scale(mut self, min: f64, max: f64) -> Self {
25        self.scale_min = min;
26        self.scale_max = max;
27        self
28    }
29    pub fn flags(mut self, flags: Surface3DFlags) -> Self {
30        self.flags = flags;
31        self
32    }
33    pub fn plot(self) {
34        self._ui.bind();
35        let x_count = match i32::try_from(self.xs.len()) {
36            Ok(v) => v,
37            Err(_) => return,
38        };
39        let y_count = match i32::try_from(self.ys.len()) {
40            Ok(v) => v,
41            Err(_) => return,
42        };
43        let expected = match self.xs.len().checked_mul(self.ys.len()) {
44            Some(v) => v,
45            None => return,
46        };
47        if self.zs.len() != expected {
48            return;
49        }
50        let label = self.label.as_ref();
51        let label = if label.contains('\0') {
52            "surface"
53        } else {
54            label
55        };
56        dear_imgui_rs::with_scratch_txt(label, |label_ptr| unsafe {
57            let spec = plot3d_spec_with_style(
58                self.style,
59                self.flags.bits() | self.item_flags.bits(),
60                Plot3DDataLayout::DEFAULT,
61            );
62            sys::ImPlot3D_PlotSurface_FloatPtr(
63                label_ptr,
64                self.xs.as_ptr(),
65                self.ys.as_ptr(),
66                self.zs.as_ptr(),
67                x_count,
68                y_count,
69                self.scale_min,
70                self.scale_max,
71                spec,
72            );
73        })
74    }
75}
76
77impl<'ui> Plot3DUi<'ui> {
78    /// Start a surface plot (f32)
79    pub fn surface_f32(
80        &'ui self,
81        label: impl Into<Cow<'ui, str>>,
82        xs: &'ui [f32],
83        ys: &'ui [f32],
84        zs: &'ui [f32],
85    ) -> Surface3DBuilder<'ui> {
86        self.bind();
87        Surface3DBuilder {
88            _ui: self,
89            label: label.into(),
90            xs,
91            ys,
92            zs,
93            scale_min: f64::NAN,
94            scale_max: f64::NAN,
95            flags: Surface3DFlags::NONE,
96            item_flags: Item3DFlags::NONE,
97            style: Plot3DItemStyle::default(),
98        }
99    }
100
101    /// Raw surface plot (f32) with an explicit data layout.
102    pub fn surface_f32_raw<S: AsRef<str>>(
103        &self,
104        label: S,
105        xs: &[f32],
106        ys: &[f32],
107        zs: &[f32],
108        scale_min: f64,
109        scale_max: f64,
110        flags: Surface3DFlags,
111        layout: Plot3DDataLayout,
112    ) {
113        self.bind();
114        debug_before_plot();
115        let x_count = xs.len();
116        let y_count = ys.len();
117        let Some(x_count_i32) = surface_count_to_i32(x_count) else {
118            return;
119        };
120        let Some(y_count_i32) = surface_count_to_i32(y_count) else {
121            return;
122        };
123        let expected = match x_count.checked_mul(y_count) {
124            Some(v) => v,
125            None => return,
126        };
127        if zs.len() != expected {
128            // Invalid grid: require zs to be x_count * y_count
129            return;
130        }
131
132        // Flatten xs/ys to per-vertex arrays expected by the C++ API (length = x_count * y_count)
133        let mut xs_flat = Vec::with_capacity(expected);
134        let mut ys_flat = Vec::with_capacity(expected);
135        for yi in 0..y_count {
136            for xi in 0..x_count {
137                xs_flat.push(xs[xi]);
138                ys_flat.push(ys[yi]);
139            }
140        }
141
142        let label = label.as_ref();
143        if label.contains('\0') {
144            return;
145        }
146        dear_imgui_rs::with_scratch_txt(label, |label_ptr| unsafe {
147            let spec = plot3d_spec_from(flags.bits(), layout);
148            sys::ImPlot3D_PlotSurface_FloatPtr(
149                label_ptr,
150                xs_flat.as_ptr(),
151                ys_flat.as_ptr(),
152                zs.as_ptr(),
153                x_count_i32,
154                y_count_i32,
155                scale_min,
156                scale_max,
157                spec,
158            );
159        })
160    }
161
162    /// Plot a surface with already flattened per-vertex X/Y arrays (no internal allocation)
163    ///
164    /// Use this when you already have per-vertex `xs_flat` and `ys_flat` of length `x_count * y_count`,
165    /// matching the layout of `zs`. This avoids per-frame allocations for large dynamic grids.
166    pub fn surface_f32_flat<S: AsRef<str>>(
167        &self,
168        label: S,
169        xs_flat: &[f32],
170        ys_flat: &[f32],
171        zs: &[f32],
172        x_count: usize,
173        y_count: usize,
174        scale_min: f64,
175        scale_max: f64,
176        flags: Surface3DFlags,
177        layout: Plot3DDataLayout,
178    ) {
179        self.bind();
180        debug_before_plot();
181        let Some(x_count_i32) = surface_count_to_i32(x_count) else {
182            return;
183        };
184        let Some(y_count_i32) = surface_count_to_i32(y_count) else {
185            return;
186        };
187        let Some(expected) = x_count.checked_mul(y_count) else {
188            return;
189        };
190        if xs_flat.len() != expected || ys_flat.len() != expected || zs.len() != expected {
191            return;
192        }
193        let label = label.as_ref();
194        if label.contains('\0') {
195            return;
196        }
197        dear_imgui_rs::with_scratch_txt(label, |label_ptr| unsafe {
198            let spec = plot3d_spec_from(flags.bits(), layout);
199            sys::ImPlot3D_PlotSurface_FloatPtr(
200                label_ptr,
201                xs_flat.as_ptr(),
202                ys_flat.as_ptr(),
203                zs.as_ptr(),
204                x_count_i32,
205                y_count_i32,
206                scale_min,
207                scale_max,
208                spec,
209            );
210        })
211    }
212}