dear_implot3d/
lib.rs

1//! Dear ImPlot3D - Rust bindings (high level)
2//!
3//! Safe wrapper over `dear-implot3d-sys`, designed to integrate with
4//! `dear-imgui-rs`. Mirrors `dear-implot` design: context + Ui facade,
5//! builder-style helpers, optional `mint` inputs.
6//!
7//! # Quick Start
8//!
9//! ```no_run
10//! use dear_imgui_rs::*;
11//! use dear_implot3d::*;
12//!
13//! let mut imgui_ctx = Context::create();
14//! let plot3d_ctx = Plot3DContext::create(&imgui_ctx);
15//!
16//! // In your main loop:
17//! let ui = imgui_ctx.frame();
18//! let plot_ui = plot3d_ctx.get_plot_ui(&ui);
19//!
20//! if let Some(_token) = plot_ui.begin_plot("3D Plot").build() {
21//!     let xs = [0.0, 1.0, 2.0];
22//!     let ys = [0.0, 1.0, 0.0];
23//!     let zs = [0.0, 0.5, 1.0];
24//!     plot_ui.plot_line_f32("Line", &xs, &ys, &zs, Line3DFlags::NONE);
25//! }
26//! ```
27//!
28//! # Features
29//!
30//! - **mint**: Enable support for `mint` math types (Point3, Vector3)
31//!
32//! # Architecture
33//!
34//! This crate follows the same design patterns as `dear-implot`:
35//! - `Plot3DContext`: Manages the ImPlot3D context (create once)
36//! - `Plot3DUi`: Per-frame access to plotting functions
37//! - RAII tokens: `Plot3DToken` automatically calls `EndPlot` on drop
38//! - Builder pattern: Fluent API for configuring plots
39//! - Type-safe flags: Using `bitflags!` for compile-time safety
40
41use dear_imgui_rs::texture::TextureRef;
42pub use dear_imgui_rs::{Context, Ui};
43use dear_implot3d_sys as sys;
44
45mod flags;
46mod style;
47mod ui_ext;
48
49pub use flags::*;
50pub use style::*;
51pub use ui_ext::*;
52pub mod meshes;
53pub mod plots;
54
55// Debug-only: enforce BeginPlot/Setup/Plot call ordering
56#[cfg(debug_assertions)]
57thread_local! {
58    static DEBUG_PLOT_STATE: PlotDebugState = PlotDebugState { in_plot: std::cell::Cell::new(false), setup_locked: std::cell::Cell::new(false) };
59}
60
61#[cfg(debug_assertions)]
62struct PlotDebugState {
63    in_plot: std::cell::Cell<bool>,
64    setup_locked: std::cell::Cell<bool>,
65}
66
67#[cfg(debug_assertions)]
68#[inline]
69fn debug_begin_plot() {
70    DEBUG_PLOT_STATE.with(|s| {
71        s.in_plot.set(true);
72        s.setup_locked.set(false);
73    });
74}
75
76#[cfg(debug_assertions)]
77#[inline]
78fn debug_end_plot() {
79    DEBUG_PLOT_STATE.with(|s| {
80        s.in_plot.set(false);
81        s.setup_locked.set(false);
82    });
83}
84
85#[cfg(debug_assertions)]
86#[inline]
87fn debug_before_setup() {
88    DEBUG_PLOT_STATE.with(|s| {
89        debug_assert!(
90            s.in_plot.get(),
91            "Setup* called outside of BeginPlot/EndPlot"
92        );
93        debug_assert!(
94            !s.setup_locked.get(),
95            "Setup* must be called before any plotting (PlotX) or locking operations"
96        );
97    });
98}
99
100#[cfg(debug_assertions)]
101#[inline]
102fn debug_before_plot() {
103    DEBUG_PLOT_STATE.with(|s| {
104        debug_assert!(s.in_plot.get(), "Plot* called outside of BeginPlot/EndPlot");
105        s.setup_locked.set(true);
106    });
107}
108
109#[cfg(not(debug_assertions))]
110#[inline]
111fn debug_begin_plot() {}
112#[cfg(not(debug_assertions))]
113#[inline]
114fn debug_end_plot() {}
115#[cfg(not(debug_assertions))]
116#[inline]
117fn debug_before_setup() {}
118#[cfg(not(debug_assertions))]
119#[inline]
120fn debug_before_plot() {}
121
122/// Show upstream ImPlot3D demos (from C++ demo)
123///
124/// This displays all available ImPlot3D demos in a single window.
125/// Useful for learning and testing the library.
126pub fn show_all_demos() {
127    unsafe { sys::ImPlot3D_ShowAllDemos() }
128}
129
130/// Show the main ImPlot3D demo window (C++ upstream)
131///
132/// This displays the main demo window with tabs for different plot types.
133/// Pass `None` to always show, or `Some(&mut bool)` to control visibility.
134///
135/// # Example
136///
137/// ```no_run
138/// use dear_implot3d::*;
139///
140/// let mut show_demo = true;
141/// show_demo_window_with_flag(&mut show_demo);
142/// ```
143pub fn show_demo_window() {
144    unsafe { sys::ImPlot3D_ShowDemoWindow(std::ptr::null_mut()) }
145}
146
147/// Show the main ImPlot3D demo window with a visibility flag
148pub fn show_demo_window_with_flag(p_open: &mut bool) {
149    unsafe { sys::ImPlot3D_ShowDemoWindow(p_open as *mut bool) }
150}
151
152/// Show the ImPlot3D style editor window
153///
154/// This displays a window for editing ImPlot3D style settings in real-time.
155/// Pass `None` to use the current style, or `Some(&mut ImPlot3DStyle)` to edit a specific style.
156pub fn show_style_editor() {
157    unsafe { sys::ImPlot3D_ShowStyleEditor(std::ptr::null_mut()) }
158}
159
160/// Show the ImPlot3D metrics/debugger window
161///
162/// This displays performance metrics and debugging information.
163/// Pass `None` to always show, or `Some(&mut bool)` to control visibility.
164pub fn show_metrics_window() {
165    unsafe { sys::ImPlot3D_ShowMetricsWindow(std::ptr::null_mut()) }
166}
167
168/// Show the ImPlot3D metrics/debugger window with a visibility flag
169pub fn show_metrics_window_with_flag(p_open: &mut bool) {
170    unsafe { sys::ImPlot3D_ShowMetricsWindow(p_open as *mut bool) }
171}
172
173/// Plot3D context wrapper
174///
175/// This manages the ImPlot3D context lifetime. Create one instance per application
176/// and keep it alive for the duration of your program.
177///
178/// # Example
179///
180/// ```no_run
181/// use dear_imgui_rs::*;
182/// use dear_implot3d::*;
183///
184/// let mut imgui_ctx = Context::create();
185/// let plot3d_ctx = Plot3DContext::create(&imgui_ctx);
186///
187/// // In your main loop:
188/// let ui = imgui_ctx.frame();
189/// let plot_ui = plot3d_ctx.get_plot_ui(&ui);
190/// ```
191pub struct Plot3DContext {
192    owned: bool,
193}
194
195impl Plot3DContext {
196    /// Create a new ImPlot3D context
197    ///
198    /// This should be called once after creating your ImGui context.
199    pub fn create(_imgui: &Context) -> Self {
200        unsafe {
201            let ctx = sys::ImPlot3D_CreateContext();
202            // Ensure our new context is set as current even if another existed
203            sys::ImPlot3D_SetCurrentContext(ctx);
204        }
205        Self { owned: true }
206    }
207
208    /// Get a raw pointer to the current ImPlot3D style
209    ///
210    /// This is an advanced function for direct style manipulation.
211    /// Prefer using the safe style functions in the `style` module.
212    pub fn raw_style_mut() -> *mut sys::ImPlot3DStyle {
213        unsafe { sys::ImPlot3D_GetStyle() }
214    }
215
216    /// Get a per-frame plotting interface
217    ///
218    /// Call this once per frame to get access to plotting functions.
219    /// The returned `Plot3DUi` is tied to the lifetime of the `Ui` frame.
220    pub fn get_plot_ui<'ui>(&self, ui: &'ui Ui) -> Plot3DUi<'ui> {
221        Plot3DUi { _ui: ui }
222    }
223}
224
225impl Drop for Plot3DContext {
226    fn drop(&mut self) {
227        if self.owned {
228            unsafe {
229                sys::ImPlot3D_DestroyContext(std::ptr::null_mut());
230            }
231        }
232    }
233}
234
235/// Per-frame access helper mirroring `dear-implot`
236///
237/// This provides access to all 3D plotting functions. It is tied to the lifetime
238/// of the current ImGui frame and should be obtained via `Plot3DContext::get_plot_ui()`.
239///
240/// # Example
241///
242/// ```no_run
243/// use dear_implot3d::*;
244///
245/// # let plot_ui: Plot3DUi = todo!();
246/// if let Some(_token) = plot_ui.begin_plot("My 3D Plot").build() {
247///     plot_ui.setup_axes("X", "Y", "Z", Axis3DFlags::NONE, Axis3DFlags::NONE, Axis3DFlags::NONE);
248///
249///     let xs = [0.0, 1.0, 2.0];
250///     let ys = [0.0, 1.0, 0.0];
251///     let zs = [0.0, 0.5, 1.0];
252///     plot_ui.plot_line_f32("Line", &xs, &ys, &zs, Line3DFlags::NONE);
253/// }
254/// ```
255pub struct Plot3DUi<'ui> {
256    _ui: &'ui Ui,
257}
258
259/// RAII token that ends the plot on drop
260///
261/// This token is returned by `Plot3DBuilder::build()` and automatically calls
262/// `ImPlot3D_EndPlot()` when it goes out of scope, ensuring proper cleanup.
263pub struct Plot3DToken;
264
265impl<'ui> Plot3DUi<'ui> {
266    /// Builder to configure and begin a 3D plot
267    ///
268    /// Returns a `Plot3DBuilder` that allows you to configure the plot before calling `.build()`.
269    ///
270    /// # Example
271    ///
272    /// ```no_run
273    /// use dear_implot3d::*;
274    ///
275    /// # let plot_ui: Plot3DUi = todo!();
276    /// if let Some(_token) = plot_ui
277    ///     .begin_plot("My Plot")
278    ///     .size([600.0, 400.0])
279    ///     .flags(Plot3DFlags::NO_LEGEND)
280    ///     .build()
281    /// {
282    ///     // Plot content here
283    /// }
284    /// ```
285    pub fn begin_plot<S: AsRef<str>>(&self, title: S) -> Plot3DBuilder {
286        Plot3DBuilder {
287            title: title.as_ref().into(),
288            size: None,
289            flags: Plot3DFlags::empty(),
290        }
291    }
292
293    /// Convenience: plot a simple 3D line (f32)
294    ///
295    /// This is a quick way to plot a line without using the builder pattern.
296    /// For more control, use the `plots::Line3D` builder.
297    ///
298    /// # Arguments
299    ///
300    /// * `label` - Label for the legend
301    /// * `xs` - X coordinates
302    /// * `ys` - Y coordinates
303    /// * `zs` - Z coordinates
304    /// * `flags` - Line flags (e.g., `Line3DFlags::SEGMENTS`, `Line3DFlags::LOOP`)
305    ///
306    /// # Example
307    ///
308    /// ```no_run
309    /// use dear_implot3d::*;
310    ///
311    /// # let plot_ui: Plot3DUi = todo!();
312    /// let xs = [0.0, 1.0, 2.0];
313    /// let ys = [0.0, 1.0, 0.0];
314    /// let zs = [0.0, 0.5, 1.0];
315    /// plot_ui.plot_line_f32("Line", &xs, &ys, &zs, Line3DFlags::NONE);
316    /// ```
317    pub fn plot_line_f32<S: AsRef<str>>(
318        &self,
319        label: S,
320        xs: &[f32],
321        ys: &[f32],
322        zs: &[f32],
323        flags: Line3DFlags,
324    ) {
325        if xs.len() != ys.len() || ys.len() != zs.len() {
326            return;
327        }
328        let label_c = match std::ffi::CString::new(label.as_ref()) {
329            Ok(s) => s,
330            Err(_) => return,
331        };
332        let stride_bytes = std::mem::size_of::<f32>() as i32;
333        unsafe {
334            sys::ImPlot3D_PlotLine_FloatPtr(
335                label_c.as_ptr(),
336                xs.as_ptr(),
337                ys.as_ptr(),
338                zs.as_ptr(),
339                xs.len() as i32,
340                flags.bits() as i32,
341                0,
342                stride_bytes,
343            );
344        }
345    }
346
347    /// Raw line plot (f32) with offset/stride
348    pub fn plot_line_f32_raw<S: AsRef<str>>(
349        &self,
350        label: S,
351        xs: &[f32],
352        ys: &[f32],
353        zs: &[f32],
354        flags: Line3DFlags,
355        offset: i32,
356        stride: i32,
357    ) {
358        if xs.len() != ys.len() || ys.len() != zs.len() {
359            return;
360        }
361        let label_c = match std::ffi::CString::new(label.as_ref()) {
362            Ok(s) => s,
363            Err(_) => return,
364        };
365        let stride_bytes = if stride == 0 {
366            std::mem::size_of::<f32>() as i32
367        } else {
368            stride
369        };
370        unsafe {
371            sys::ImPlot3D_PlotLine_FloatPtr(
372                label_c.as_ptr(),
373                xs.as_ptr(),
374                ys.as_ptr(),
375                zs.as_ptr(),
376                xs.len() as i32,
377                flags.bits() as i32,
378                offset,
379                stride_bytes,
380            );
381        }
382    }
383
384    /// Convenience: plot a simple 3D line (f64)
385    pub fn plot_line_f64<S: AsRef<str>>(
386        &self,
387        label: S,
388        xs: &[f64],
389        ys: &[f64],
390        zs: &[f64],
391        flags: Line3DFlags,
392    ) {
393        if xs.len() != ys.len() || ys.len() != zs.len() {
394            return;
395        }
396        let label_c = match std::ffi::CString::new(label.as_ref()) {
397            Ok(s) => s,
398            Err(_) => return,
399        };
400        let stride_bytes = std::mem::size_of::<f64>() as i32;
401        unsafe {
402            sys::ImPlot3D_PlotLine_doublePtr(
403                label_c.as_ptr(),
404                xs.as_ptr(),
405                ys.as_ptr(),
406                zs.as_ptr(),
407                xs.len() as i32,
408                flags.bits() as i32,
409                0,
410                stride_bytes,
411            );
412        }
413    }
414
415    /// Raw line plot (f64) with offset/stride
416    pub fn plot_line_f64_raw<S: AsRef<str>>(
417        &self,
418        label: S,
419        xs: &[f64],
420        ys: &[f64],
421        zs: &[f64],
422        flags: Line3DFlags,
423        offset: i32,
424        stride: i32,
425    ) {
426        if xs.len() != ys.len() || ys.len() != zs.len() {
427            return;
428        }
429        let label_c = match std::ffi::CString::new(label.as_ref()) {
430            Ok(s) => s,
431            Err(_) => return,
432        };
433        let stride_bytes = if stride == 0 {
434            std::mem::size_of::<f64>() as i32
435        } else {
436            stride
437        };
438        unsafe {
439            sys::ImPlot3D_PlotLine_doublePtr(
440                label_c.as_ptr(),
441                xs.as_ptr(),
442                ys.as_ptr(),
443                zs.as_ptr(),
444                xs.len() as i32,
445                flags.bits() as i32,
446                offset,
447                stride_bytes,
448            );
449        }
450    }
451
452    /// Convenience: plot a 3D scatter (f32)
453    pub fn plot_scatter_f32<S: AsRef<str>>(
454        &self,
455        label: S,
456        xs: &[f32],
457        ys: &[f32],
458        zs: &[f32],
459        flags: Scatter3DFlags,
460    ) {
461        if xs.len() != ys.len() || ys.len() != zs.len() {
462            return;
463        }
464        let label_c = match std::ffi::CString::new(label.as_ref()) {
465            Ok(s) => s,
466            Err(_) => return,
467        };
468        let stride_bytes = std::mem::size_of::<f32>() as i32;
469        unsafe {
470            sys::ImPlot3D_PlotScatter_FloatPtr(
471                label_c.as_ptr(),
472                xs.as_ptr(),
473                ys.as_ptr(),
474                zs.as_ptr(),
475                xs.len() as i32,
476                flags.bits() as i32,
477                0,
478                stride_bytes,
479            );
480        }
481    }
482
483    /// Raw scatter plot (f32) with offset/stride
484    pub fn plot_scatter_f32_raw<S: AsRef<str>>(
485        &self,
486        label: S,
487        xs: &[f32],
488        ys: &[f32],
489        zs: &[f32],
490        flags: Scatter3DFlags,
491        offset: i32,
492        stride: i32,
493    ) {
494        if xs.len() != ys.len() || ys.len() != zs.len() {
495            return;
496        }
497        let label_c = match std::ffi::CString::new(label.as_ref()) {
498            Ok(s) => s,
499            Err(_) => return,
500        };
501        let stride_bytes = if stride == 0 {
502            std::mem::size_of::<f32>() as i32
503        } else {
504            stride
505        };
506        unsafe {
507            sys::ImPlot3D_PlotScatter_FloatPtr(
508                label_c.as_ptr(),
509                xs.as_ptr(),
510                ys.as_ptr(),
511                zs.as_ptr(),
512                xs.len() as i32,
513                flags.bits() as i32,
514                offset,
515                stride_bytes,
516            );
517        }
518    }
519
520    /// Convenience: plot a 3D scatter (f64)
521    pub fn plot_scatter_f64<S: AsRef<str>>(
522        &self,
523        label: S,
524        xs: &[f64],
525        ys: &[f64],
526        zs: &[f64],
527        flags: Scatter3DFlags,
528    ) {
529        if xs.len() != ys.len() || ys.len() != zs.len() {
530            return;
531        }
532        let label_c = match std::ffi::CString::new(label.as_ref()) {
533            Ok(s) => s,
534            Err(_) => return,
535        };
536        let stride_bytes = std::mem::size_of::<f64>() as i32;
537        unsafe {
538            sys::ImPlot3D_PlotScatter_doublePtr(
539                label_c.as_ptr(),
540                xs.as_ptr(),
541                ys.as_ptr(),
542                zs.as_ptr(),
543                xs.len() as i32,
544                flags.bits() as i32,
545                0,
546                stride_bytes,
547            );
548        }
549    }
550
551    /// Raw scatter plot (f64) with offset/stride
552    pub fn plot_scatter_f64_raw<S: AsRef<str>>(
553        &self,
554        label: S,
555        xs: &[f64],
556        ys: &[f64],
557        zs: &[f64],
558        flags: Scatter3DFlags,
559        offset: i32,
560        stride: i32,
561    ) {
562        if xs.len() != ys.len() || ys.len() != zs.len() {
563            return;
564        }
565        let label_c = match std::ffi::CString::new(label.as_ref()) {
566            Ok(s) => s,
567            Err(_) => return,
568        };
569        let stride_bytes = if stride == 0 {
570            std::mem::size_of::<f64>() as i32
571        } else {
572            stride
573        };
574        unsafe {
575            sys::ImPlot3D_PlotScatter_doublePtr(
576                label_c.as_ptr(),
577                xs.as_ptr(),
578                ys.as_ptr(),
579                zs.as_ptr(),
580                xs.len() as i32,
581                flags.bits() as i32,
582                offset,
583                stride_bytes,
584            );
585        }
586    }
587
588    /// Convenience: plot triangles from interleaved xyz arrays (count must be multiple of 3)
589    pub fn plot_triangles_f32<S: AsRef<str>>(
590        &self,
591        label: S,
592        xs: &[f32],
593        ys: &[f32],
594        zs: &[f32],
595        flags: Triangle3DFlags,
596    ) {
597        if xs.len() != ys.len() || ys.len() != zs.len() {
598            return;
599        }
600        let label_c = match std::ffi::CString::new(label.as_ref()) {
601            Ok(s) => s,
602            Err(_) => return,
603        };
604        let stride_bytes = std::mem::size_of::<f32>() as i32;
605        unsafe {
606            sys::ImPlot3D_PlotTriangle_FloatPtr(
607                label_c.as_ptr(),
608                xs.as_ptr(),
609                ys.as_ptr(),
610                zs.as_ptr(),
611                xs.len() as i32,
612                flags.bits() as i32,
613                0,
614                stride_bytes,
615            );
616        }
617    }
618
619    pub fn plot_triangles_f32_raw<S: AsRef<str>>(
620        &self,
621        label: S,
622        xs: &[f32],
623        ys: &[f32],
624        zs: &[f32],
625        flags: Triangle3DFlags,
626        offset: i32,
627        stride: i32,
628    ) {
629        if xs.len() != ys.len() || ys.len() != zs.len() {
630            return;
631        }
632        let label_c = match std::ffi::CString::new(label.as_ref()) {
633            Ok(s) => s,
634            Err(_) => return,
635        };
636        let stride_bytes = if stride == 0 {
637            std::mem::size_of::<f32>() as i32
638        } else {
639            stride
640        };
641        unsafe {
642            sys::ImPlot3D_PlotTriangle_FloatPtr(
643                label_c.as_ptr(),
644                xs.as_ptr(),
645                ys.as_ptr(),
646                zs.as_ptr(),
647                xs.len() as i32,
648                flags.bits() as i32,
649                offset,
650                stride_bytes,
651            );
652        }
653    }
654
655    /// Convenience: plot quads from interleaved xyz arrays (count must be multiple of 4)
656    pub fn plot_quads_f32<S: AsRef<str>>(
657        &self,
658        label: S,
659        xs: &[f32],
660        ys: &[f32],
661        zs: &[f32],
662        flags: Quad3DFlags,
663    ) {
664        if xs.len() != ys.len() || ys.len() != zs.len() {
665            return;
666        }
667        let label_c = match std::ffi::CString::new(label.as_ref()) {
668            Ok(s) => s,
669            Err(_) => return,
670        };
671        let stride_bytes = std::mem::size_of::<f32>() as i32;
672        unsafe {
673            sys::ImPlot3D_PlotQuad_FloatPtr(
674                label_c.as_ptr(),
675                xs.as_ptr(),
676                ys.as_ptr(),
677                zs.as_ptr(),
678                xs.len() as i32,
679                flags.bits() as i32,
680                0,
681                stride_bytes,
682            );
683        }
684    }
685
686    pub fn plot_quads_f32_raw<S: AsRef<str>>(
687        &self,
688        label: S,
689        xs: &[f32],
690        ys: &[f32],
691        zs: &[f32],
692        flags: Quad3DFlags,
693        offset: i32,
694        stride: i32,
695    ) {
696        if xs.len() != ys.len() || ys.len() != zs.len() {
697            return;
698        }
699        let label_c = match std::ffi::CString::new(label.as_ref()) {
700            Ok(s) => s,
701            Err(_) => return,
702        };
703        let stride_bytes = if stride == 0 {
704            std::mem::size_of::<f32>() as i32
705        } else {
706            stride
707        };
708        unsafe {
709            sys::ImPlot3D_PlotQuad_FloatPtr(
710                label_c.as_ptr(),
711                xs.as_ptr(),
712                ys.as_ptr(),
713                zs.as_ptr(),
714                xs.len() as i32,
715                flags.bits() as i32,
716                offset,
717                stride_bytes,
718            );
719        }
720    }
721
722    /// Convenience: plot triangles from interleaved xyz arrays (f64)
723    pub fn plot_triangles_f64<S: AsRef<str>>(
724        &self,
725        label: S,
726        xs: &[f64],
727        ys: &[f64],
728        zs: &[f64],
729        flags: Triangle3DFlags,
730    ) {
731        if xs.len() != ys.len() || ys.len() != zs.len() {
732            return;
733        }
734        let label_c = match std::ffi::CString::new(label.as_ref()) {
735            Ok(s) => s,
736            Err(_) => return,
737        };
738        let stride_bytes = std::mem::size_of::<f64>() as i32;
739        unsafe {
740            sys::ImPlot3D_PlotTriangle_doublePtr(
741                label_c.as_ptr(),
742                xs.as_ptr(),
743                ys.as_ptr(),
744                zs.as_ptr(),
745                xs.len() as i32,
746                flags.bits() as i32,
747                0,
748                stride_bytes,
749            );
750        }
751    }
752
753    pub fn plot_triangles_f64_raw<S: AsRef<str>>(
754        &self,
755        label: S,
756        xs: &[f64],
757        ys: &[f64],
758        zs: &[f64],
759        flags: Triangle3DFlags,
760        offset: i32,
761        stride: i32,
762    ) {
763        if xs.len() != ys.len() || ys.len() != zs.len() {
764            return;
765        }
766        let label_c = match std::ffi::CString::new(label.as_ref()) {
767            Ok(s) => s,
768            Err(_) => return,
769        };
770        let stride_bytes = if stride == 0 {
771            std::mem::size_of::<f64>() as i32
772        } else {
773            stride
774        };
775        unsafe {
776            sys::ImPlot3D_PlotTriangle_doublePtr(
777                label_c.as_ptr(),
778                xs.as_ptr(),
779                ys.as_ptr(),
780                zs.as_ptr(),
781                xs.len() as i32,
782                flags.bits() as i32,
783                offset,
784                stride_bytes,
785            );
786        }
787    }
788
789    /// Convenience: plot quads from interleaved xyz arrays (f64)
790    pub fn plot_quads_f64<S: AsRef<str>>(
791        &self,
792        label: S,
793        xs: &[f64],
794        ys: &[f64],
795        zs: &[f64],
796        flags: Quad3DFlags,
797    ) {
798        if xs.len() != ys.len() || ys.len() != zs.len() {
799            return;
800        }
801        let label_c = match std::ffi::CString::new(label.as_ref()) {
802            Ok(s) => s,
803            Err(_) => return,
804        };
805        let stride_bytes = std::mem::size_of::<f64>() as i32;
806        unsafe {
807            sys::ImPlot3D_PlotQuad_doublePtr(
808                label_c.as_ptr(),
809                xs.as_ptr(),
810                ys.as_ptr(),
811                zs.as_ptr(),
812                xs.len() as i32,
813                flags.bits() as i32,
814                0,
815                stride_bytes,
816            );
817        }
818    }
819
820    pub fn plot_quads_f64_raw<S: AsRef<str>>(
821        &self,
822        label: S,
823        xs: &[f64],
824        ys: &[f64],
825        zs: &[f64],
826        flags: Quad3DFlags,
827        offset: i32,
828        stride: i32,
829    ) {
830        if xs.len() != ys.len() || ys.len() != zs.len() {
831            return;
832        }
833        let label_c = match std::ffi::CString::new(label.as_ref()) {
834            Ok(s) => s,
835            Err(_) => return,
836        };
837        let stride_bytes = if stride == 0 {
838            std::mem::size_of::<f64>() as i32
839        } else {
840            stride
841        };
842        unsafe {
843            sys::ImPlot3D_PlotQuad_doublePtr(
844                label_c.as_ptr(),
845                xs.as_ptr(),
846                ys.as_ptr(),
847                zs.as_ptr(),
848                xs.len() as i32,
849                flags.bits() as i32,
850                offset,
851                stride_bytes,
852            );
853        }
854    }
855}
856
857impl Drop for Plot3DToken {
858    fn drop(&mut self) {
859        unsafe {
860            debug_end_plot();
861            sys::ImPlot3D_EndPlot();
862        }
863    }
864}
865
866/// Plot builder for configuring the 3D plot
867pub struct Plot3DBuilder {
868    title: String,
869    size: Option<[f32; 2]>,
870    flags: Plot3DFlags,
871}
872
873impl Plot3DBuilder {
874    pub fn size(mut self, size: [f32; 2]) -> Self {
875        self.size = Some(size);
876        self
877    }
878    pub fn flags(mut self, flags: Plot3DFlags) -> Self {
879        self.flags = flags;
880        self
881    }
882    pub fn build(self) -> Option<Plot3DToken> {
883        let title_c = std::ffi::CString::new(self.title).ok()?;
884        let size = self.size.unwrap_or([0.0, 0.0]);
885        let ok = unsafe {
886            // Defensive: ensure style.Colormap is in range before plotting
887            let style = sys::ImPlot3D_GetStyle();
888            if !style.is_null() {
889                let count = sys::ImPlot3D_GetColormapCount();
890                if count > 0 {
891                    if (*style).Colormap < 0 || (*style).Colormap >= count {
892                        (*style).Colormap = 0;
893                    }
894                }
895            }
896            sys::ImPlot3D_BeginPlot(
897                title_c.as_ptr(),
898                sys::ImVec2 {
899                    x: size[0],
900                    y: size[1],
901                },
902                self.flags.bits() as i32,
903            )
904        };
905        if ok {
906            debug_begin_plot();
907            Some(Plot3DToken)
908        } else {
909            None
910        }
911    }
912}
913
914/// Optional mint support for inputs
915///
916/// When the `mint` feature is enabled, you can use `mint::Point3<f32>` and `mint::Vector3<f32>`
917/// types directly with plotting functions. This provides interoperability with popular math
918/// libraries like `glam`, `nalgebra`, `cgmath`, etc.
919///
920/// # Example
921///
922/// ```no_run
923/// # #[cfg(feature = "mint")]
924/// # {
925/// use dear_implot3d::*;
926/// use mint::Point3;
927///
928/// # let plot_ui: Plot3DUi = todo!();
929/// let points = vec![
930///     Point3 { x: 0.0, y: 0.0, z: 0.0 },
931///     Point3 { x: 1.0, y: 1.0, z: 1.0 },
932///     Point3 { x: 2.0, y: 0.0, z: 2.0 },
933/// ];
934///
935/// if let Some(_token) = plot_ui.begin_plot("Mint Example").build() {
936///     plot_ui.plot_line_mint("Line", &points, Line3DFlags::NONE);
937/// }
938/// # }
939/// ```
940#[cfg(feature = "mint")]
941impl<'ui> Plot3DUi<'ui> {
942    /// Plot a 3D line using `mint::Point3<f32>` points
943    ///
944    /// This is a convenience function that converts mint points to separate x, y, z arrays.
945    pub fn plot_line_mint<S: AsRef<str>>(
946        &self,
947        label: S,
948        pts: &[mint::Point3<f32>],
949        flags: Line3DFlags,
950    ) {
951        let mut xs = Vec::with_capacity(pts.len());
952        let mut ys = Vec::with_capacity(pts.len());
953        let mut zs = Vec::with_capacity(pts.len());
954        for p in pts {
955            xs.push(p.x);
956            ys.push(p.y);
957            zs.push(p.z);
958        }
959        self.plot_line_f32(label, &xs, &ys, &zs, flags);
960    }
961
962    /// Plot a 3D scatter using `mint::Point3<f32>` points
963    pub fn plot_scatter_mint<S: AsRef<str>>(
964        &self,
965        label: S,
966        pts: &[mint::Point3<f32>],
967        flags: Scatter3DFlags,
968    ) {
969        let mut xs = Vec::with_capacity(pts.len());
970        let mut ys = Vec::with_capacity(pts.len());
971        let mut zs = Vec::with_capacity(pts.len());
972        for p in pts {
973            xs.push(p.x);
974            ys.push(p.y);
975            zs.push(p.z);
976        }
977        self.plot_scatter_f32(label, &xs, &ys, &zs, flags);
978    }
979
980    /// Plot 3D text at a `mint::Point3<f32>` position
981    pub fn plot_text_mint(
982        &self,
983        text: &str,
984        pos: mint::Point3<f32>,
985        angle: f32,
986        pix_offset: [f32; 2],
987    ) {
988        self.plot_text(text, pos.x, pos.y, pos.z, angle, pix_offset);
989    }
990
991    /// Convert a `mint::Point3<f32>` to pixel coordinates
992    pub fn plot_to_pixels_mint(&self, point: mint::Point3<f32>) -> [f32; 2] {
993        self.plot_to_pixels([point.x, point.y, point.z])
994    }
995}
996
997/// Surface (grid) plot builder (f32 variant)
998pub struct Surface3DBuilder<'ui> {
999    ui: &'ui Plot3DUi<'ui>,
1000    label: std::ffi::CString,
1001    xs: &'ui [f32],
1002    ys: &'ui [f32],
1003    zs: &'ui [f32],
1004    scale_min: f64,
1005    scale_max: f64,
1006    flags: Surface3DFlags,
1007}
1008
1009impl<'ui> Surface3DBuilder<'ui> {
1010    pub fn scale(mut self, min: f64, max: f64) -> Self {
1011        self.scale_min = min;
1012        self.scale_max = max;
1013        self
1014    }
1015    pub fn flags(mut self, flags: Surface3DFlags) -> Self {
1016        self.flags = flags;
1017        self
1018    }
1019    pub fn plot(self) {
1020        let x_count = self.xs.len() as i32;
1021        let y_count = self.ys.len() as i32;
1022        let expected = (x_count as usize) * (y_count as usize);
1023        if self.zs.len() != expected {
1024            return;
1025        }
1026        unsafe {
1027            sys::ImPlot3D_PlotSurface_FloatPtr(
1028                self.label.as_ptr(),
1029                self.xs.as_ptr(),
1030                self.ys.as_ptr(),
1031                self.zs.as_ptr(),
1032                x_count,
1033                y_count,
1034                self.scale_min,
1035                self.scale_max,
1036                self.flags.bits() as i32,
1037                0,
1038                0,
1039            );
1040        }
1041    }
1042}
1043
1044impl<'ui> Plot3DUi<'ui> {
1045    /// Start a surface plot (f32)
1046    pub fn surface_f32<S: AsRef<str>>(
1047        &'ui self,
1048        label: S,
1049        xs: &'ui [f32],
1050        ys: &'ui [f32],
1051        zs: &'ui [f32],
1052    ) -> Surface3DBuilder<'ui> {
1053        let label_c = std::ffi::CString::new(label.as_ref())
1054            .unwrap_or_else(|_| std::ffi::CString::new("surface").unwrap());
1055        Surface3DBuilder {
1056            ui: self,
1057            label: label_c,
1058            xs,
1059            ys,
1060            zs,
1061            scale_min: f64::NAN,
1062            scale_max: f64::NAN,
1063            flags: Surface3DFlags::NONE,
1064        }
1065    }
1066
1067    /// Raw surface plot (f32) with offset/stride
1068    pub fn surface_f32_raw<S: AsRef<str>>(
1069        &self,
1070        label: S,
1071        xs: &[f32],
1072        ys: &[f32],
1073        zs: &[f32],
1074        scale_min: f64,
1075        scale_max: f64,
1076        flags: Surface3DFlags,
1077        offset: i32,
1078        stride: i32,
1079    ) {
1080        debug_before_plot();
1081        let x_count = xs.len();
1082        let y_count = ys.len();
1083        let expected = match x_count.checked_mul(y_count) {
1084            Some(v) => v,
1085            None => return,
1086        };
1087        if zs.len() != expected {
1088            // Invalid grid: require zs to be x_count * y_count
1089            return;
1090        }
1091
1092        // Flatten xs/ys to per-vertex arrays expected by the C++ API (length = x_count * y_count)
1093        let mut xs_flat = Vec::with_capacity(expected);
1094        let mut ys_flat = Vec::with_capacity(expected);
1095        for yi in 0..y_count {
1096            for xi in 0..x_count {
1097                xs_flat.push(xs[xi]);
1098                ys_flat.push(ys[yi]);
1099            }
1100        }
1101
1102        let label_c = match std::ffi::CString::new(label.as_ref()) {
1103            Ok(s) => s,
1104            Err(_) => return,
1105        };
1106        let stride_bytes = if stride == 0 {
1107            std::mem::size_of::<f32>() as i32
1108        } else {
1109            stride
1110        };
1111        unsafe {
1112            sys::ImPlot3D_PlotSurface_FloatPtr(
1113                label_c.as_ptr(),
1114                xs_flat.as_ptr(),
1115                ys_flat.as_ptr(),
1116                zs.as_ptr(),
1117                x_count as i32,
1118                y_count as i32,
1119                scale_min,
1120                scale_max,
1121                flags.bits() as i32,
1122                offset,
1123                stride_bytes,
1124            );
1125        }
1126    }
1127
1128    /// Plot a surface with already flattened per-vertex X/Y arrays (no internal allocation)
1129    ///
1130    /// Use this when you already have per-vertex `xs_flat` and `ys_flat` of length `x_count * y_count`,
1131    /// matching the layout of `zs`. This avoids per-frame allocations for large dynamic grids.
1132    pub fn surface_f32_flat<S: AsRef<str>>(
1133        &self,
1134        label: S,
1135        xs_flat: &[f32],
1136        ys_flat: &[f32],
1137        zs: &[f32],
1138        x_count: i32,
1139        y_count: i32,
1140        scale_min: f64,
1141        scale_max: f64,
1142        flags: Surface3DFlags,
1143        offset: i32,
1144        stride: i32,
1145    ) {
1146        debug_before_plot();
1147        if x_count <= 0 || y_count <= 0 {
1148            return;
1149        }
1150        let expected = (x_count as usize).saturating_mul(y_count as usize);
1151        if xs_flat.len() != expected || ys_flat.len() != expected || zs.len() != expected {
1152            return;
1153        }
1154        let label_c = match std::ffi::CString::new(label.as_ref()) {
1155            Ok(s) => s,
1156            Err(_) => return,
1157        };
1158        let stride_bytes = if stride == 0 {
1159            std::mem::size_of::<f32>() as i32
1160        } else {
1161            stride
1162        };
1163        unsafe {
1164            sys::ImPlot3D_PlotSurface_FloatPtr(
1165                label_c.as_ptr(),
1166                xs_flat.as_ptr(),
1167                ys_flat.as_ptr(),
1168                zs.as_ptr(),
1169                x_count,
1170                y_count,
1171                scale_min,
1172                scale_max,
1173                flags.bits() as i32,
1174                offset,
1175                stride_bytes,
1176            );
1177        }
1178    }
1179}
1180
1181/// Image by axes builder
1182pub struct Image3DByAxesBuilder<'ui> {
1183    _ui: &'ui Plot3DUi<'ui>,
1184    label: std::ffi::CString,
1185    tex_ref: sys::ImTextureRef,
1186    center: [f32; 3],
1187    axis_u: [f32; 3],
1188    axis_v: [f32; 3],
1189    uv0: [f32; 2],
1190    uv1: [f32; 2],
1191    tint: [f32; 4],
1192    flags: Image3DFlags,
1193}
1194
1195impl<'ui> Image3DByAxesBuilder<'ui> {
1196    pub fn uv(mut self, uv0: [f32; 2], uv1: [f32; 2]) -> Self {
1197        self.uv0 = uv0;
1198        self.uv1 = uv1;
1199        self
1200    }
1201    pub fn tint(mut self, col: [f32; 4]) -> Self {
1202        self.tint = col;
1203        self
1204    }
1205    pub fn flags(mut self, flags: Image3DFlags) -> Self {
1206        self.flags = flags;
1207        self
1208    }
1209    pub fn plot(self) {
1210        unsafe {
1211            debug_before_plot();
1212            sys::ImPlot3D_PlotImage_Vec2(
1213                self.label.as_ptr(),
1214                self.tex_ref,
1215                sys::ImPlot3DPoint {
1216                    x: self.center[0],
1217                    y: self.center[1],
1218                    z: self.center[2],
1219                },
1220                sys::ImPlot3DPoint {
1221                    x: self.axis_u[0],
1222                    y: self.axis_u[1],
1223                    z: self.axis_u[2],
1224                },
1225                sys::ImPlot3DPoint {
1226                    x: self.axis_v[0],
1227                    y: self.axis_v[1],
1228                    z: self.axis_v[2],
1229                },
1230                sys::ImVec2 {
1231                    x: self.uv0[0],
1232                    y: self.uv0[1],
1233                },
1234                sys::ImVec2 {
1235                    x: self.uv1[0],
1236                    y: self.uv1[1],
1237                },
1238                sys::ImVec4 {
1239                    x: self.tint[0],
1240                    y: self.tint[1],
1241                    z: self.tint[2],
1242                    w: self.tint[3],
1243                },
1244                self.flags.bits() as i32,
1245            );
1246        }
1247    }
1248}
1249
1250/// Image by corners builder
1251pub struct Image3DByCornersBuilder<'ui> {
1252    _ui: &'ui Plot3DUi<'ui>,
1253    label: std::ffi::CString,
1254    tex_ref: sys::ImTextureRef,
1255    p0: [f32; 3],
1256    p1: [f32; 3],
1257    p2: [f32; 3],
1258    p3: [f32; 3],
1259    uv0: [f32; 2],
1260    uv1: [f32; 2],
1261    uv2: [f32; 2],
1262    uv3: [f32; 2],
1263    tint: [f32; 4],
1264    flags: Image3DFlags,
1265}
1266
1267impl<'ui> Image3DByCornersBuilder<'ui> {
1268    pub fn uvs(mut self, uv0: [f32; 2], uv1: [f32; 2], uv2: [f32; 2], uv3: [f32; 2]) -> Self {
1269        self.uv0 = uv0;
1270        self.uv1 = uv1;
1271        self.uv2 = uv2;
1272        self.uv3 = uv3;
1273        self
1274    }
1275    pub fn tint(mut self, col: [f32; 4]) -> Self {
1276        self.tint = col;
1277        self
1278    }
1279    pub fn flags(mut self, flags: Image3DFlags) -> Self {
1280        self.flags = flags;
1281        self
1282    }
1283    pub fn plot(self) {
1284        unsafe {
1285            debug_before_plot();
1286            sys::ImPlot3D_PlotImage_Plot3DPoInt(
1287                self.label.as_ptr(),
1288                self.tex_ref,
1289                sys::ImPlot3DPoint {
1290                    x: self.p0[0],
1291                    y: self.p0[1],
1292                    z: self.p0[2],
1293                },
1294                sys::ImPlot3DPoint {
1295                    x: self.p1[0],
1296                    y: self.p1[1],
1297                    z: self.p1[2],
1298                },
1299                sys::ImPlot3DPoint {
1300                    x: self.p2[0],
1301                    y: self.p2[1],
1302                    z: self.p2[2],
1303                },
1304                sys::ImPlot3DPoint {
1305                    x: self.p3[0],
1306                    y: self.p3[1],
1307                    z: self.p3[2],
1308                },
1309                sys::ImVec2 {
1310                    x: self.uv0[0],
1311                    y: self.uv0[1],
1312                },
1313                sys::ImVec2 {
1314                    x: self.uv1[0],
1315                    y: self.uv1[1],
1316                },
1317                sys::ImVec2 {
1318                    x: self.uv2[0],
1319                    y: self.uv2[1],
1320                },
1321                sys::ImVec2 {
1322                    x: self.uv3[0],
1323                    y: self.uv3[1],
1324                },
1325                sys::ImVec4 {
1326                    x: self.tint[0],
1327                    y: self.tint[1],
1328                    z: self.tint[2],
1329                    w: self.tint[3],
1330                },
1331                self.flags.bits() as i32,
1332            );
1333        }
1334    }
1335}
1336
1337impl<'ui> Plot3DUi<'ui> {
1338    /// Image oriented by center and axes
1339    pub fn image_by_axes<S: AsRef<str>, T: Into<TextureRef>>(
1340        &'ui self,
1341        label: S,
1342        tex: T,
1343        center: [f32; 3],
1344        axis_u: [f32; 3],
1345        axis_v: [f32; 3],
1346    ) -> Image3DByAxesBuilder<'ui> {
1347        let label_c = std::ffi::CString::new(label.as_ref())
1348            .unwrap_or_else(|_| std::ffi::CString::new("image").unwrap());
1349        let tr = tex.into().raw();
1350        let tex_ref = sys::ImTextureRef {
1351            _TexData: tr._TexData as *mut sys::ImTextureData,
1352            _TexID: tr._TexID as sys::ImTextureID,
1353        };
1354        debug_before_plot();
1355        Image3DByAxesBuilder {
1356            _ui: self,
1357            label: label_c,
1358            tex_ref,
1359            center,
1360            axis_u,
1361            axis_v,
1362            uv0: [0.0, 0.0],
1363            uv1: [1.0, 1.0],
1364            tint: [1.0, 1.0, 1.0, 1.0],
1365            flags: Image3DFlags::NONE,
1366        }
1367    }
1368
1369    /// Image by 4 corner points (p0..p3)
1370    pub fn image_by_corners<S: AsRef<str>, T: Into<TextureRef>>(
1371        &'ui self,
1372        label: S,
1373        tex: T,
1374        p0: [f32; 3],
1375        p1: [f32; 3],
1376        p2: [f32; 3],
1377        p3: [f32; 3],
1378    ) -> Image3DByCornersBuilder<'ui> {
1379        let label_c = std::ffi::CString::new(label.as_ref())
1380            .unwrap_or_else(|_| std::ffi::CString::new("image").unwrap());
1381        let tr = tex.into().raw();
1382        let tex_ref = sys::ImTextureRef {
1383            _TexData: tr._TexData as *mut sys::ImTextureData,
1384            _TexID: tr._TexID as sys::ImTextureID,
1385        };
1386        debug_before_plot();
1387        Image3DByCornersBuilder {
1388            _ui: self,
1389            label: label_c,
1390            tex_ref,
1391            p0,
1392            p1,
1393            p2,
1394            p3,
1395            uv0: [0.0, 0.0],
1396            uv1: [1.0, 0.0],
1397            uv2: [1.0, 1.0],
1398            uv3: [0.0, 1.0],
1399            tint: [1.0, 1.0, 1.0, 1.0],
1400            flags: Image3DFlags::NONE,
1401        }
1402    }
1403}
1404
1405/// Axis helpers
1406impl<'ui> Plot3DUi<'ui> {
1407    pub fn setup_axes(
1408        &self,
1409        x_label: &str,
1410        y_label: &str,
1411        z_label: &str,
1412        x_flags: Axis3DFlags,
1413        y_flags: Axis3DFlags,
1414        z_flags: Axis3DFlags,
1415    ) {
1416        debug_before_setup();
1417        let cx = std::ffi::CString::new(x_label).unwrap_or_default();
1418        let cy = std::ffi::CString::new(y_label).unwrap_or_default();
1419        let cz = std::ffi::CString::new(z_label).unwrap_or_default();
1420        unsafe {
1421            sys::ImPlot3D_SetupAxes(
1422                cx.as_ptr(),
1423                cy.as_ptr(),
1424                cz.as_ptr(),
1425                x_flags.bits() as i32,
1426                y_flags.bits() as i32,
1427                z_flags.bits() as i32,
1428            )
1429        }
1430    }
1431
1432    pub fn setup_axis(&self, axis: Axis3D, label: &str, flags: Axis3DFlags) {
1433        debug_before_setup();
1434        let c = std::ffi::CString::new(label).unwrap_or_default();
1435        unsafe { sys::ImPlot3D_SetupAxis(axis as i32, c.as_ptr(), flags.bits() as i32) }
1436    }
1437
1438    pub fn setup_axis_limits(&self, axis: Axis3D, min: f64, max: f64, cond: Plot3DCond) {
1439        debug_before_setup();
1440        unsafe { sys::ImPlot3D_SetupAxisLimits(axis as i32, min, max, cond as i32) }
1441    }
1442
1443    pub fn setup_axes_limits(
1444        &self,
1445        x_min: f64,
1446        x_max: f64,
1447        y_min: f64,
1448        y_max: f64,
1449        z_min: f64,
1450        z_max: f64,
1451        cond: Plot3DCond,
1452    ) {
1453        debug_before_setup();
1454        unsafe {
1455            sys::ImPlot3D_SetupAxesLimits(x_min, x_max, y_min, y_max, z_min, z_max, cond as i32)
1456        }
1457    }
1458
1459    pub fn setup_axis_limits_constraints(&self, axis: Axis3D, v_min: f64, v_max: f64) {
1460        debug_before_setup();
1461        unsafe { sys::ImPlot3D_SetupAxisLimitsConstraints(axis as i32, v_min, v_max) }
1462    }
1463
1464    pub fn setup_axis_zoom_constraints(&self, axis: Axis3D, z_min: f64, z_max: f64) {
1465        debug_before_setup();
1466        unsafe { sys::ImPlot3D_SetupAxisZoomConstraints(axis as i32, z_min, z_max) }
1467    }
1468
1469    pub fn setup_axis_ticks_values(
1470        &self,
1471        axis: Axis3D,
1472        values: &[f64],
1473        labels: Option<&[&str]>,
1474        keep_default: bool,
1475    ) {
1476        debug_before_setup();
1477        let n_ticks = values.len() as i32;
1478        let labels_ptr = if let Some(lbls) = labels {
1479            let c_labels: Vec<std::ffi::CString> = lbls
1480                .iter()
1481                .map(|s| std::ffi::CString::new(*s).unwrap_or_default())
1482                .collect();
1483            let ptrs: Vec<*const i8> = c_labels.iter().map(|cs| cs.as_ptr()).collect();
1484            ptrs.as_ptr()
1485        } else {
1486            std::ptr::null()
1487        };
1488        unsafe {
1489            sys::ImPlot3D_SetupAxisTicks_doublePtr(
1490                axis as i32,
1491                values.as_ptr(),
1492                n_ticks,
1493                labels_ptr,
1494                keep_default,
1495            )
1496        }
1497    }
1498
1499    pub fn setup_axis_ticks_range(
1500        &self,
1501        axis: Axis3D,
1502        v_min: f64,
1503        v_max: f64,
1504        n_ticks: i32,
1505        labels: Option<&[&str]>,
1506        keep_default: bool,
1507    ) {
1508        debug_before_setup();
1509        let labels_ptr = if let Some(lbls) = labels {
1510            let c_labels: Vec<std::ffi::CString> = lbls
1511                .iter()
1512                .map(|s| std::ffi::CString::new(*s).unwrap_or_default())
1513                .collect();
1514            let ptrs: Vec<*const i8> = c_labels.iter().map(|cs| cs.as_ptr()).collect();
1515            ptrs.as_ptr()
1516        } else {
1517            std::ptr::null()
1518        };
1519        unsafe {
1520            sys::ImPlot3D_SetupAxisTicks_double(
1521                axis as i32,
1522                v_min,
1523                v_max,
1524                n_ticks,
1525                labels_ptr,
1526                keep_default,
1527            )
1528        }
1529    }
1530
1531    pub fn setup_box_scale(&self, x: f32, y: f32, z: f32) {
1532        debug_before_setup();
1533        unsafe { sys::ImPlot3D_SetupBoxScale(x, y, z) }
1534    }
1535
1536    pub fn setup_box_rotation(
1537        &self,
1538        elevation: f32,
1539        azimuth: f32,
1540        animate: bool,
1541        cond: Plot3DCond,
1542    ) {
1543        debug_before_setup();
1544        unsafe { sys::ImPlot3D_SetupBoxRotation_Float(elevation, azimuth, animate, cond as i32) }
1545    }
1546
1547    pub fn setup_box_initial_rotation(&self, elevation: f32, azimuth: f32) {
1548        debug_before_setup();
1549        unsafe { sys::ImPlot3D_SetupBoxInitialRotation_Float(elevation, azimuth) }
1550    }
1551
1552    pub fn plot_text(&self, text: &str, x: f32, y: f32, z: f32, angle: f32, pix_offset: [f32; 2]) {
1553        let c_text = std::ffi::CString::new(text).unwrap_or_default();
1554        unsafe {
1555            debug_before_plot();
1556            sys::ImPlot3D_PlotText(
1557                c_text.as_ptr(),
1558                x,
1559                y,
1560                z,
1561                angle,
1562                sys::ImVec2 {
1563                    x: pix_offset[0],
1564                    y: pix_offset[1],
1565                },
1566            )
1567        }
1568    }
1569
1570    pub fn plot_to_pixels(&self, point: [f32; 3]) -> [f32; 2] {
1571        unsafe {
1572            let mut out = sys::ImVec2 { x: 0.0, y: 0.0 };
1573            sys::ImPlot3D_PlotToPixels_double(
1574                &mut out,
1575                point[0] as f64,
1576                point[1] as f64,
1577                point[2] as f64,
1578            );
1579            [out.x, out.y]
1580        }
1581    }
1582
1583    pub fn get_plot_draw_list(&self) -> *mut sys::ImDrawList {
1584        unsafe { sys::ImPlot3D_GetPlotDrawList() }
1585    }
1586
1587    pub fn get_frame_pos(&self) -> [f32; 2] {
1588        unsafe {
1589            let mut out = sys::ImVec2 { x: 0.0, y: 0.0 };
1590            sys::ImPlot3D_GetPlotPos(&mut out);
1591            [out.x, out.y]
1592        }
1593    }
1594
1595    pub fn get_frame_size(&self) -> [f32; 2] {
1596        unsafe {
1597            let mut out = sys::ImVec2 { x: 0.0, y: 0.0 };
1598            sys::ImPlot3D_GetPlotSize(&mut out);
1599            [out.x, out.y]
1600        }
1601    }
1602}
1603
1604/// Mesh plot builder
1605pub struct Mesh3DBuilder<'ui> {
1606    _ui: &'ui Plot3DUi<'ui>,
1607    label: std::ffi::CString,
1608    vertices: &'ui [[f32; 3]],
1609    indices: &'ui [u32],
1610    flags: Mesh3DFlags,
1611}
1612
1613impl<'ui> Mesh3DBuilder<'ui> {
1614    pub fn flags(mut self, flags: Mesh3DFlags) -> Self {
1615        self.flags = flags;
1616        self
1617    }
1618    pub fn plot(self) {
1619        // SAFETY: ImPlot3DPoint has (x,y,z) floats; we transmute from [[f32;3]] for FFI call
1620        // Layout compatibility assumed; if upstream changes, this needs revisiting.
1621        let vtx_count = self.vertices.len() as i32;
1622        let idx_count = self.indices.len() as i32;
1623        unsafe {
1624            debug_before_plot();
1625            let vtx_ptr = self.vertices.as_ptr() as *const sys::ImPlot3DPoint;
1626            sys::ImPlot3D_PlotMesh(
1627                self.label.as_ptr(),
1628                vtx_ptr,
1629                self.indices.as_ptr(),
1630                vtx_count,
1631                idx_count,
1632                self.flags.bits() as i32,
1633            );
1634        }
1635    }
1636}
1637
1638impl<'ui> Plot3DUi<'ui> {
1639    /// Start a mesh plot from vertices (x,y,z) and triangle indices
1640    pub fn mesh<S: AsRef<str>>(
1641        &'ui self,
1642        label: S,
1643        vertices: &'ui [[f32; 3]],
1644        indices: &'ui [u32],
1645    ) -> Mesh3DBuilder<'ui> {
1646        let label_c = std::ffi::CString::new(label.as_ref())
1647            .unwrap_or_else(|_| std::ffi::CString::new("mesh").unwrap());
1648        Mesh3DBuilder {
1649            _ui: self,
1650            label: label_c,
1651            vertices,
1652            indices,
1653            flags: Mesh3DFlags::NONE,
1654        }
1655    }
1656}