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