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