Skip to main content

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