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        unsafe {
365            sys::ImPlot3D_SetCurrentContext(self.raw);
366        }
367    }
368
369    /// Get a raw pointer to the current ImPlot3D style
370    ///
371    /// This is an advanced function for direct style manipulation.
372    /// Prefer using the safe style functions in the `style` module.
373    pub fn raw_style_mut() -> *mut sys::ImPlot3DStyle {
374        unsafe { sys::ImPlot3D_GetStyle() }
375    }
376
377    /// Get a per-frame plotting interface
378    ///
379    /// Call this once per frame to get access to plotting functions.
380    /// The returned `Plot3DUi` is tied to the lifetime of the `Ui` frame.
381    pub fn get_plot_ui<'ui>(&self, ui: &'ui Ui) -> Plot3DUi<'ui> {
382        Plot3DUi { _ui: ui }
383    }
384}
385
386impl Drop for Plot3DContext {
387    fn drop(&mut self) {
388        if self.raw.is_null() {
389            return;
390        }
391
392        if !self.imgui_alive.is_alive() {
393            // Avoid calling into ImGui allocators after the context has been dropped.
394            // Best-effort: leak the Plot3D context instead of risking UB.
395            return;
396        }
397
398        unsafe {
399            let prev_imgui = imgui_sys::igGetCurrentContext();
400            imgui_sys::igSetCurrentContext(self.imgui_ctx_raw);
401
402            if sys::ImPlot3D_GetCurrentContext() == self.raw {
403                sys::ImPlot3D_SetCurrentContext(std::ptr::null_mut());
404            }
405            sys::ImPlot3D_DestroyContext(self.raw);
406
407            imgui_sys::igSetCurrentContext(prev_imgui);
408        }
409    }
410}
411
412/// Per-frame access helper mirroring `dear-implot`
413///
414/// This provides access to all 3D plotting functions. It is tied to the lifetime
415/// of the current ImGui frame and should be obtained via `Plot3DContext::get_plot_ui()`.
416///
417/// # Example
418///
419/// ```no_run
420/// use dear_implot3d::*;
421///
422/// # let plot_ui: Plot3DUi = todo!();
423/// if let Some(_token) = plot_ui.begin_plot("My 3D Plot").build() {
424///     plot_ui.setup_axes("X", "Y", "Z", Axis3DFlags::NONE, Axis3DFlags::NONE, Axis3DFlags::NONE);
425///
426///     let xs = [0.0, 1.0, 2.0];
427///     let ys = [0.0, 1.0, 0.0];
428///     let zs = [0.0, 0.5, 1.0];
429///     plot_ui.plot_line_f32("Line", &xs, &ys, &zs, Line3DFlags::NONE);
430/// }
431/// ```
432pub struct Plot3DUi<'ui> {
433    _ui: &'ui Ui,
434}
435
436/// RAII token that ends the plot on drop
437///
438/// This token is returned by `Plot3DBuilder::build()` and automatically calls
439/// `ImPlot3D_EndPlot()` when it goes out of scope, ensuring proper cleanup.
440pub struct Plot3DToken;
441
442impl<'ui> Plot3DUi<'ui> {
443    /// Builder to configure and begin a 3D plot
444    ///
445    /// Returns a `Plot3DBuilder` that allows you to configure the plot before calling `.build()`.
446    ///
447    /// # Example
448    ///
449    /// ```no_run
450    /// use dear_implot3d::*;
451    ///
452    /// # let plot_ui: Plot3DUi = todo!();
453    /// if let Some(_token) = plot_ui
454    ///     .begin_plot("My Plot")
455    ///     .size([600.0, 400.0])
456    ///     .flags(Plot3DFlags::NO_LEGEND)
457    ///     .build()
458    /// {
459    ///     // Plot content here
460    /// }
461    /// ```
462    pub fn begin_plot<S: AsRef<str>>(&self, title: S) -> Plot3DBuilder {
463        Plot3DBuilder {
464            title: title.as_ref().into(),
465            size: None,
466            flags: Plot3DFlags::empty(),
467        }
468    }
469
470    /// Convenience: plot a simple 3D line (f32)
471    ///
472    /// This is a quick way to plot a line without using the builder pattern.
473    /// For more control, use the `plots::Line3D` builder.
474    ///
475    /// # Arguments
476    ///
477    /// * `label` - Label for the legend
478    /// * `xs` - X coordinates
479    /// * `ys` - Y coordinates
480    /// * `zs` - Z coordinates
481    /// * `flags` - Line flags (e.g., `Line3DFlags::SEGMENTS`, `Line3DFlags::LOOP`)
482    ///
483    /// # Example
484    ///
485    /// ```no_run
486    /// use dear_implot3d::*;
487    ///
488    /// # let plot_ui: Plot3DUi = todo!();
489    /// let xs = [0.0, 1.0, 2.0];
490    /// let ys = [0.0, 1.0, 0.0];
491    /// let zs = [0.0, 0.5, 1.0];
492    /// plot_ui.plot_line_f32("Line", &xs, &ys, &zs, Line3DFlags::NONE);
493    /// ```
494    pub fn plot_line_f32<S: AsRef<str>>(
495        &self,
496        label: S,
497        xs: &[f32],
498        ys: &[f32],
499        zs: &[f32],
500        flags: Line3DFlags,
501    ) {
502        if xs.len() != ys.len() || ys.len() != zs.len() {
503            return;
504        }
505        let Some(count) = len_i32(xs.len()) else {
506            return;
507        };
508        let label = label.as_ref();
509        if label.contains('\0') {
510            return;
511        }
512        let stride_bytes = std::mem::size_of::<f32>() as i32;
513        dear_imgui_rs::with_scratch_txt(label, |label_ptr| unsafe {
514            let spec = plot3d_spec_from(flags.bits(), 0, stride_bytes);
515            sys::ImPlot3D_PlotLine_FloatPtr(
516                label_ptr,
517                xs.as_ptr(),
518                ys.as_ptr(),
519                zs.as_ptr(),
520                count,
521                spec,
522            );
523        })
524    }
525
526    /// Raw line plot (f32) with offset/stride
527    pub fn plot_line_f32_raw<S: AsRef<str>>(
528        &self,
529        label: S,
530        xs: &[f32],
531        ys: &[f32],
532        zs: &[f32],
533        flags: Line3DFlags,
534        offset: i32,
535        stride: i32,
536    ) {
537        if xs.len() != ys.len() || ys.len() != zs.len() {
538            return;
539        }
540        let Some(count) = len_i32(xs.len()) else {
541            return;
542        };
543        let label = label.as_ref();
544        if label.contains('\0') {
545            return;
546        }
547        let stride_bytes = if stride == 0 {
548            std::mem::size_of::<f32>() as i32
549        } else {
550            stride
551        };
552        dear_imgui_rs::with_scratch_txt(label, |label_ptr| unsafe {
553            let spec = plot3d_spec_from(flags.bits(), offset, stride_bytes);
554            sys::ImPlot3D_PlotLine_FloatPtr(
555                label_ptr,
556                xs.as_ptr(),
557                ys.as_ptr(),
558                zs.as_ptr(),
559                count,
560                spec,
561            );
562        })
563    }
564
565    /// Convenience: plot a simple 3D line (f64)
566    pub fn plot_line_f64<S: AsRef<str>>(
567        &self,
568        label: S,
569        xs: &[f64],
570        ys: &[f64],
571        zs: &[f64],
572        flags: Line3DFlags,
573    ) {
574        if xs.len() != ys.len() || ys.len() != zs.len() {
575            return;
576        }
577        let Some(count) = len_i32(xs.len()) else {
578            return;
579        };
580        let label = label.as_ref();
581        if label.contains('\0') {
582            return;
583        }
584        let stride_bytes = std::mem::size_of::<f64>() as i32;
585        dear_imgui_rs::with_scratch_txt(label, |label_ptr| unsafe {
586            let spec = plot3d_spec_from(flags.bits(), 0, stride_bytes);
587            sys::ImPlot3D_PlotLine_doublePtr(
588                label_ptr,
589                xs.as_ptr(),
590                ys.as_ptr(),
591                zs.as_ptr(),
592                count,
593                spec,
594            );
595        })
596    }
597
598    /// Raw line plot (f64) with offset/stride
599    pub fn plot_line_f64_raw<S: AsRef<str>>(
600        &self,
601        label: S,
602        xs: &[f64],
603        ys: &[f64],
604        zs: &[f64],
605        flags: Line3DFlags,
606        offset: i32,
607        stride: i32,
608    ) {
609        if xs.len() != ys.len() || ys.len() != zs.len() {
610            return;
611        }
612        let Some(count) = len_i32(xs.len()) else {
613            return;
614        };
615        let label = label.as_ref();
616        if label.contains('\0') {
617            return;
618        }
619        let stride_bytes = if stride == 0 {
620            std::mem::size_of::<f64>() as i32
621        } else {
622            stride
623        };
624        dear_imgui_rs::with_scratch_txt(label, |label_ptr| unsafe {
625            let spec = plot3d_spec_from(flags.bits(), offset, stride_bytes);
626            sys::ImPlot3D_PlotLine_doublePtr(
627                label_ptr,
628                xs.as_ptr(),
629                ys.as_ptr(),
630                zs.as_ptr(),
631                count,
632                spec,
633            );
634        })
635    }
636
637    /// Convenience: plot a 3D scatter (f32)
638    pub fn plot_scatter_f32<S: AsRef<str>>(
639        &self,
640        label: S,
641        xs: &[f32],
642        ys: &[f32],
643        zs: &[f32],
644        flags: Scatter3DFlags,
645    ) {
646        if xs.len() != ys.len() || ys.len() != zs.len() {
647            return;
648        }
649        let Some(count) = len_i32(xs.len()) else {
650            return;
651        };
652        let label = label.as_ref();
653        if label.contains('\0') {
654            return;
655        }
656        let stride_bytes = std::mem::size_of::<f32>() as i32;
657        dear_imgui_rs::with_scratch_txt(label, |label_ptr| unsafe {
658            let spec = plot3d_spec_from(flags.bits(), 0, stride_bytes);
659            sys::ImPlot3D_PlotScatter_FloatPtr(
660                label_ptr,
661                xs.as_ptr(),
662                ys.as_ptr(),
663                zs.as_ptr(),
664                count,
665                spec,
666            );
667        })
668    }
669
670    /// Raw scatter plot (f32) with offset/stride
671    pub fn plot_scatter_f32_raw<S: AsRef<str>>(
672        &self,
673        label: S,
674        xs: &[f32],
675        ys: &[f32],
676        zs: &[f32],
677        flags: Scatter3DFlags,
678        offset: i32,
679        stride: i32,
680    ) {
681        if xs.len() != ys.len() || ys.len() != zs.len() {
682            return;
683        }
684        let Some(count) = len_i32(xs.len()) else {
685            return;
686        };
687        let label = label.as_ref();
688        if label.contains('\0') {
689            return;
690        }
691        let stride_bytes = if stride == 0 {
692            std::mem::size_of::<f32>() as i32
693        } else {
694            stride
695        };
696        dear_imgui_rs::with_scratch_txt(label, |label_ptr| unsafe {
697            let spec = plot3d_spec_from(flags.bits(), offset, stride_bytes);
698            sys::ImPlot3D_PlotScatter_FloatPtr(
699                label_ptr,
700                xs.as_ptr(),
701                ys.as_ptr(),
702                zs.as_ptr(),
703                count,
704                spec,
705            );
706        })
707    }
708
709    /// Convenience: plot a 3D scatter (f64)
710    pub fn plot_scatter_f64<S: AsRef<str>>(
711        &self,
712        label: S,
713        xs: &[f64],
714        ys: &[f64],
715        zs: &[f64],
716        flags: Scatter3DFlags,
717    ) {
718        if xs.len() != ys.len() || ys.len() != zs.len() {
719            return;
720        }
721        let Some(count) = len_i32(xs.len()) else {
722            return;
723        };
724        let label = label.as_ref();
725        if label.contains('\0') {
726            return;
727        }
728        let stride_bytes = std::mem::size_of::<f64>() as i32;
729        dear_imgui_rs::with_scratch_txt(label, |label_ptr| unsafe {
730            let spec = plot3d_spec_from(flags.bits(), 0, stride_bytes);
731            sys::ImPlot3D_PlotScatter_doublePtr(
732                label_ptr,
733                xs.as_ptr(),
734                ys.as_ptr(),
735                zs.as_ptr(),
736                count,
737                spec,
738            );
739        })
740    }
741
742    /// Raw scatter plot (f64) with offset/stride
743    pub fn plot_scatter_f64_raw<S: AsRef<str>>(
744        &self,
745        label: S,
746        xs: &[f64],
747        ys: &[f64],
748        zs: &[f64],
749        flags: Scatter3DFlags,
750        offset: i32,
751        stride: i32,
752    ) {
753        if xs.len() != ys.len() || ys.len() != zs.len() {
754            return;
755        }
756        let Some(count) = len_i32(xs.len()) else {
757            return;
758        };
759        let label = label.as_ref();
760        if label.contains('\0') {
761            return;
762        }
763        let stride_bytes = if stride == 0 {
764            std::mem::size_of::<f64>() as i32
765        } else {
766            stride
767        };
768        dear_imgui_rs::with_scratch_txt(label, |label_ptr| unsafe {
769            let spec = plot3d_spec_from(flags.bits(), offset, stride_bytes);
770            sys::ImPlot3D_PlotScatter_doublePtr(
771                label_ptr,
772                xs.as_ptr(),
773                ys.as_ptr(),
774                zs.as_ptr(),
775                count,
776                spec,
777            );
778        })
779    }
780
781    /// Convenience: plot triangles from interleaved xyz arrays (count must be multiple of 3)
782    pub fn plot_triangles_f32<S: AsRef<str>>(
783        &self,
784        label: S,
785        xs: &[f32],
786        ys: &[f32],
787        zs: &[f32],
788        flags: Triangle3DFlags,
789    ) {
790        if xs.len() != ys.len() || ys.len() != zs.len() {
791            return;
792        }
793        let Some(count) = len_i32(xs.len()) else {
794            return;
795        };
796        let label = label.as_ref();
797        if label.contains('\0') {
798            return;
799        }
800        let stride_bytes = std::mem::size_of::<f32>() as i32;
801        dear_imgui_rs::with_scratch_txt(label, |label_ptr| unsafe {
802            let spec = plot3d_spec_from(flags.bits(), 0, 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    pub fn plot_triangles_f32_raw<S: AsRef<str>>(
815        &self,
816        label: S,
817        xs: &[f32],
818        ys: &[f32],
819        zs: &[f32],
820        flags: Triangle3DFlags,
821        offset: i32,
822        stride: i32,
823    ) {
824        if xs.len() != ys.len() || ys.len() != zs.len() {
825            return;
826        }
827        let Some(count) = len_i32(xs.len()) else {
828            return;
829        };
830        let label = label.as_ref();
831        if label.contains('\0') {
832            return;
833        }
834        let stride_bytes = if stride == 0 {
835            std::mem::size_of::<f32>() as i32
836        } else {
837            stride
838        };
839        dear_imgui_rs::with_scratch_txt(label, |label_ptr| unsafe {
840            let spec = plot3d_spec_from(flags.bits(), offset, stride_bytes);
841            sys::ImPlot3D_PlotTriangle_FloatPtr(
842                label_ptr,
843                xs.as_ptr(),
844                ys.as_ptr(),
845                zs.as_ptr(),
846                count,
847                spec,
848            );
849        })
850    }
851
852    /// Convenience: plot quads from interleaved xyz arrays (count must be multiple of 4)
853    pub fn plot_quads_f32<S: AsRef<str>>(
854        &self,
855        label: S,
856        xs: &[f32],
857        ys: &[f32],
858        zs: &[f32],
859        flags: Quad3DFlags,
860    ) {
861        if xs.len() != ys.len() || ys.len() != zs.len() {
862            return;
863        }
864        let Some(count) = len_i32(xs.len()) else {
865            return;
866        };
867        let label = label.as_ref();
868        if label.contains('\0') {
869            return;
870        }
871        let stride_bytes = std::mem::size_of::<f32>() as i32;
872        dear_imgui_rs::with_scratch_txt(label, |label_ptr| unsafe {
873            let spec = plot3d_spec_from(flags.bits(), 0, 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    pub fn plot_quads_f32_raw<S: AsRef<str>>(
886        &self,
887        label: S,
888        xs: &[f32],
889        ys: &[f32],
890        zs: &[f32],
891        flags: Quad3DFlags,
892        offset: i32,
893        stride: i32,
894    ) {
895        if xs.len() != ys.len() || ys.len() != zs.len() {
896            return;
897        }
898        let Some(count) = len_i32(xs.len()) else {
899            return;
900        };
901        let label = label.as_ref();
902        if label.contains('\0') {
903            return;
904        }
905        let stride_bytes = if stride == 0 {
906            std::mem::size_of::<f32>() as i32
907        } else {
908            stride
909        };
910        dear_imgui_rs::with_scratch_txt(label, |label_ptr| unsafe {
911            let spec = plot3d_spec_from(flags.bits(), offset, stride_bytes);
912            sys::ImPlot3D_PlotQuad_FloatPtr(
913                label_ptr,
914                xs.as_ptr(),
915                ys.as_ptr(),
916                zs.as_ptr(),
917                count,
918                spec,
919            );
920        })
921    }
922
923    /// Convenience: plot triangles from interleaved xyz arrays (f64)
924    pub fn plot_triangles_f64<S: AsRef<str>>(
925        &self,
926        label: S,
927        xs: &[f64],
928        ys: &[f64],
929        zs: &[f64],
930        flags: Triangle3DFlags,
931    ) {
932        if xs.len() != ys.len() || ys.len() != zs.len() {
933            return;
934        }
935        let Some(count) = len_i32(xs.len()) else {
936            return;
937        };
938        let label = label.as_ref();
939        if label.contains('\0') {
940            return;
941        }
942        let stride_bytes = std::mem::size_of::<f64>() as i32;
943        dear_imgui_rs::with_scratch_txt(label, |label_ptr| unsafe {
944            let spec = plot3d_spec_from(flags.bits(), 0, 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    pub fn plot_triangles_f64_raw<S: AsRef<str>>(
957        &self,
958        label: S,
959        xs: &[f64],
960        ys: &[f64],
961        zs: &[f64],
962        flags: Triangle3DFlags,
963        offset: i32,
964        stride: i32,
965    ) {
966        if xs.len() != ys.len() || ys.len() != zs.len() {
967            return;
968        }
969        let Some(count) = len_i32(xs.len()) else {
970            return;
971        };
972        let label = label.as_ref();
973        if label.contains('\0') {
974            return;
975        }
976        let stride_bytes = if stride == 0 {
977            std::mem::size_of::<f64>() as i32
978        } else {
979            stride
980        };
981        dear_imgui_rs::with_scratch_txt(label, |label_ptr| unsafe {
982            let spec = plot3d_spec_from(flags.bits(), offset, stride_bytes);
983            sys::ImPlot3D_PlotTriangle_doublePtr(
984                label_ptr,
985                xs.as_ptr(),
986                ys.as_ptr(),
987                zs.as_ptr(),
988                count,
989                spec,
990            );
991        })
992    }
993
994    /// Convenience: plot quads from interleaved xyz arrays (f64)
995    pub fn plot_quads_f64<S: AsRef<str>>(
996        &self,
997        label: S,
998        xs: &[f64],
999        ys: &[f64],
1000        zs: &[f64],
1001        flags: Quad3DFlags,
1002    ) {
1003        if xs.len() != ys.len() || ys.len() != zs.len() {
1004            return;
1005        }
1006        let Some(count) = len_i32(xs.len()) else {
1007            return;
1008        };
1009        let label = label.as_ref();
1010        if label.contains('\0') {
1011            return;
1012        }
1013        let stride_bytes = std::mem::size_of::<f64>() as i32;
1014        dear_imgui_rs::with_scratch_txt(label, |label_ptr| unsafe {
1015            let spec = plot3d_spec_from(flags.bits(), 0, 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    pub fn plot_quads_f64_raw<S: AsRef<str>>(
1028        &self,
1029        label: S,
1030        xs: &[f64],
1031        ys: &[f64],
1032        zs: &[f64],
1033        flags: Quad3DFlags,
1034        offset: i32,
1035        stride: i32,
1036    ) {
1037        if xs.len() != ys.len() || ys.len() != zs.len() {
1038            return;
1039        }
1040        let Some(count) = len_i32(xs.len()) else {
1041            return;
1042        };
1043        let label = label.as_ref();
1044        if label.contains('\0') {
1045            return;
1046        }
1047        let stride_bytes = if stride == 0 {
1048            std::mem::size_of::<f64>() as i32
1049        } else {
1050            stride
1051        };
1052        dear_imgui_rs::with_scratch_txt(label, |label_ptr| unsafe {
1053            let spec = plot3d_spec_from(flags.bits(), offset, stride_bytes);
1054            sys::ImPlot3D_PlotQuad_doublePtr(
1055                label_ptr,
1056                xs.as_ptr(),
1057                ys.as_ptr(),
1058                zs.as_ptr(),
1059                count,
1060                spec,
1061            );
1062        })
1063    }
1064}
1065
1066impl Drop for Plot3DToken {
1067    fn drop(&mut self) {
1068        unsafe {
1069            debug_end_plot();
1070            sys::ImPlot3D_EndPlot();
1071        }
1072    }
1073}
1074
1075/// Plot builder for configuring the 3D plot
1076pub struct Plot3DBuilder {
1077    title: String,
1078    size: Option<[f32; 2]>,
1079    flags: Plot3DFlags,
1080}
1081
1082impl Plot3DBuilder {
1083    pub fn size(mut self, size: [f32; 2]) -> Self {
1084        self.size = Some(size);
1085        self
1086    }
1087    pub fn flags(mut self, flags: Plot3DFlags) -> Self {
1088        self.flags = flags;
1089        self
1090    }
1091    pub fn build(self) -> Option<Plot3DToken> {
1092        if self.title.contains('\0') {
1093            return None;
1094        }
1095        let title = self.title;
1096        let size = self.size.unwrap_or([0.0, 0.0]);
1097        let ok = dear_imgui_rs::with_scratch_txt(&title, |title_ptr| unsafe {
1098            // Defensive: ensure style.Colormap is in range before plotting
1099            let style = sys::ImPlot3D_GetStyle();
1100            if !style.is_null() {
1101                let count = sys::ImPlot3D_GetColormapCount();
1102                if count > 0 && ((*style).Colormap < 0 || (*style).Colormap >= count) {
1103                    (*style).Colormap = 0;
1104                }
1105            }
1106            sys::ImPlot3D_BeginPlot(
1107                title_ptr,
1108                imvec2(size[0], size[1]),
1109                self.flags.bits() as i32,
1110            )
1111        });
1112        if ok {
1113            debug_begin_plot();
1114            Some(Plot3DToken)
1115        } else {
1116            None
1117        }
1118    }
1119}
1120
1121/// Optional mint support for inputs
1122///
1123/// When the `mint` feature is enabled, you can use `mint::Point3<f32>` and `mint::Vector3<f32>`
1124/// types directly with plotting functions. This provides interoperability with popular math
1125/// libraries like `glam`, `nalgebra`, `cgmath`, etc.
1126///
1127/// # Example
1128///
1129/// ```no_run
1130/// # #[cfg(feature = "mint")]
1131/// # {
1132/// use dear_implot3d::*;
1133/// use mint::Point3;
1134///
1135/// # let plot_ui: Plot3DUi = todo!();
1136/// let points = vec![
1137///     Point3 { x: 0.0, y: 0.0, z: 0.0 },
1138///     Point3 { x: 1.0, y: 1.0, z: 1.0 },
1139///     Point3 { x: 2.0, y: 0.0, z: 2.0 },
1140/// ];
1141///
1142/// if let Some(_token) = plot_ui.begin_plot("Mint Example").build() {
1143///     plot_ui.plot_line_mint("Line", &points, Line3DFlags::NONE);
1144/// }
1145/// # }
1146/// ```
1147#[cfg(feature = "mint")]
1148impl<'ui> Plot3DUi<'ui> {
1149    /// Plot a 3D line using `mint::Point3<f32>` points
1150    ///
1151    /// This is a convenience function that converts mint points to separate x, y, z arrays.
1152    pub fn plot_line_mint<S: AsRef<str>>(
1153        &self,
1154        label: S,
1155        pts: &[mint::Point3<f32>],
1156        flags: Line3DFlags,
1157    ) {
1158        let mut xs = Vec::with_capacity(pts.len());
1159        let mut ys = Vec::with_capacity(pts.len());
1160        let mut zs = Vec::with_capacity(pts.len());
1161        for p in pts {
1162            xs.push(p.x);
1163            ys.push(p.y);
1164            zs.push(p.z);
1165        }
1166        self.plot_line_f32(label, &xs, &ys, &zs, flags);
1167    }
1168
1169    /// Plot a 3D scatter using `mint::Point3<f32>` points
1170    pub fn plot_scatter_mint<S: AsRef<str>>(
1171        &self,
1172        label: S,
1173        pts: &[mint::Point3<f32>],
1174        flags: Scatter3DFlags,
1175    ) {
1176        let mut xs = Vec::with_capacity(pts.len());
1177        let mut ys = Vec::with_capacity(pts.len());
1178        let mut zs = Vec::with_capacity(pts.len());
1179        for p in pts {
1180            xs.push(p.x);
1181            ys.push(p.y);
1182            zs.push(p.z);
1183        }
1184        self.plot_scatter_f32(label, &xs, &ys, &zs, flags);
1185    }
1186
1187    /// Plot 3D text at a `mint::Point3<f32>` position
1188    pub fn plot_text_mint(
1189        &self,
1190        text: &str,
1191        pos: mint::Point3<f32>,
1192        angle: f32,
1193        pix_offset: [f32; 2],
1194    ) {
1195        self.plot_text(text, pos.x, pos.y, pos.z, angle, pix_offset);
1196    }
1197
1198    /// Convert a `mint::Point3<f32>` to pixel coordinates
1199    pub fn plot_to_pixels_mint(&self, point: mint::Point3<f32>) -> [f32; 2] {
1200        self.plot_to_pixels([point.x, point.y, point.z])
1201    }
1202}
1203
1204/// Surface (grid) plot builder (f32 variant)
1205pub struct Surface3DBuilder<'ui> {
1206    _ui: &'ui Plot3DUi<'ui>,
1207    label: Cow<'ui, str>,
1208    xs: &'ui [f32],
1209    ys: &'ui [f32],
1210    zs: &'ui [f32],
1211    scale_min: f64,
1212    scale_max: f64,
1213    flags: Surface3DFlags,
1214    item_flags: Item3DFlags,
1215    style: Plot3DItemStyle,
1216}
1217
1218impl<'ui> Surface3DBuilder<'ui> {
1219    pub fn scale(mut self, min: f64, max: f64) -> Self {
1220        self.scale_min = min;
1221        self.scale_max = max;
1222        self
1223    }
1224    pub fn flags(mut self, flags: Surface3DFlags) -> Self {
1225        self.flags = flags;
1226        self
1227    }
1228    pub fn plot(self) {
1229        let x_count = match i32::try_from(self.xs.len()) {
1230            Ok(v) => v,
1231            Err(_) => return,
1232        };
1233        let y_count = match i32::try_from(self.ys.len()) {
1234            Ok(v) => v,
1235            Err(_) => return,
1236        };
1237        let expected = match self.xs.len().checked_mul(self.ys.len()) {
1238            Some(v) => v,
1239            None => return,
1240        };
1241        if self.zs.len() != expected {
1242            return;
1243        }
1244        let label = self.label.as_ref();
1245        let label = if label.contains('\0') {
1246            "surface"
1247        } else {
1248            label
1249        };
1250        dear_imgui_rs::with_scratch_txt(label, |label_ptr| unsafe {
1251            let spec = plot3d_spec_with_style(
1252                self.style,
1253                self.flags.bits() | self.item_flags.bits(),
1254                0,
1255                std::mem::size_of::<f32>() as i32,
1256            );
1257            sys::ImPlot3D_PlotSurface_FloatPtr(
1258                label_ptr,
1259                self.xs.as_ptr(),
1260                self.ys.as_ptr(),
1261                self.zs.as_ptr(),
1262                x_count,
1263                y_count,
1264                self.scale_min,
1265                self.scale_max,
1266                spec,
1267            );
1268        })
1269    }
1270}
1271
1272impl<'ui> Plot3DUi<'ui> {
1273    /// Start a surface plot (f32)
1274    pub fn surface_f32(
1275        &'ui self,
1276        label: impl Into<Cow<'ui, str>>,
1277        xs: &'ui [f32],
1278        ys: &'ui [f32],
1279        zs: &'ui [f32],
1280    ) -> Surface3DBuilder<'ui> {
1281        Surface3DBuilder {
1282            _ui: self,
1283            label: label.into(),
1284            xs,
1285            ys,
1286            zs,
1287            scale_min: f64::NAN,
1288            scale_max: f64::NAN,
1289            flags: Surface3DFlags::NONE,
1290            item_flags: Item3DFlags::NONE,
1291            style: Plot3DItemStyle::default(),
1292        }
1293    }
1294
1295    /// Raw surface plot (f32) with offset/stride
1296    pub fn surface_f32_raw<S: AsRef<str>>(
1297        &self,
1298        label: S,
1299        xs: &[f32],
1300        ys: &[f32],
1301        zs: &[f32],
1302        scale_min: f64,
1303        scale_max: f64,
1304        flags: Surface3DFlags,
1305        offset: i32,
1306        stride: i32,
1307    ) {
1308        debug_before_plot();
1309        let x_count = xs.len();
1310        let y_count = ys.len();
1311        let expected = match x_count.checked_mul(y_count) {
1312            Some(v) => v,
1313            None => return,
1314        };
1315        if zs.len() != expected {
1316            // Invalid grid: require zs to be x_count * y_count
1317            return;
1318        }
1319
1320        // Flatten xs/ys to per-vertex arrays expected by the C++ API (length = x_count * y_count)
1321        let mut xs_flat = Vec::with_capacity(expected);
1322        let mut ys_flat = Vec::with_capacity(expected);
1323        for yi in 0..y_count {
1324            for xi in 0..x_count {
1325                xs_flat.push(xs[xi]);
1326                ys_flat.push(ys[yi]);
1327            }
1328        }
1329
1330        let label = label.as_ref();
1331        if label.contains('\0') {
1332            return;
1333        }
1334        let stride_bytes = if stride == 0 {
1335            std::mem::size_of::<f32>() as i32
1336        } else {
1337            stride
1338        };
1339        dear_imgui_rs::with_scratch_txt(label, |label_ptr| unsafe {
1340            let spec = plot3d_spec_from(flags.bits(), offset, stride_bytes);
1341            sys::ImPlot3D_PlotSurface_FloatPtr(
1342                label_ptr,
1343                xs_flat.as_ptr(),
1344                ys_flat.as_ptr(),
1345                zs.as_ptr(),
1346                x_count as i32,
1347                y_count as i32,
1348                scale_min,
1349                scale_max,
1350                spec,
1351            );
1352        })
1353    }
1354
1355    /// Plot a surface with already flattened per-vertex X/Y arrays (no internal allocation)
1356    ///
1357    /// Use this when you already have per-vertex `xs_flat` and `ys_flat` of length `x_count * y_count`,
1358    /// matching the layout of `zs`. This avoids per-frame allocations for large dynamic grids.
1359    pub fn surface_f32_flat<S: AsRef<str>>(
1360        &self,
1361        label: S,
1362        xs_flat: &[f32],
1363        ys_flat: &[f32],
1364        zs: &[f32],
1365        x_count: i32,
1366        y_count: i32,
1367        scale_min: f64,
1368        scale_max: f64,
1369        flags: Surface3DFlags,
1370        offset: i32,
1371        stride: i32,
1372    ) {
1373        debug_before_plot();
1374        if x_count <= 0 || y_count <= 0 {
1375            return;
1376        }
1377        let expected = (x_count as usize).saturating_mul(y_count as usize);
1378        if xs_flat.len() != expected || ys_flat.len() != expected || zs.len() != expected {
1379            return;
1380        }
1381        let label = label.as_ref();
1382        if label.contains('\0') {
1383            return;
1384        }
1385        let stride_bytes = if stride == 0 {
1386            std::mem::size_of::<f32>() as i32
1387        } else {
1388            stride
1389        };
1390        dear_imgui_rs::with_scratch_txt(label, |label_ptr| unsafe {
1391            let spec = plot3d_spec_from(flags.bits(), offset, stride_bytes);
1392            sys::ImPlot3D_PlotSurface_FloatPtr(
1393                label_ptr,
1394                xs_flat.as_ptr(),
1395                ys_flat.as_ptr(),
1396                zs.as_ptr(),
1397                x_count,
1398                y_count,
1399                scale_min,
1400                scale_max,
1401                spec,
1402            );
1403        })
1404    }
1405}
1406
1407/// Image by axes builder
1408pub struct Image3DByAxesBuilder<'ui> {
1409    _ui: &'ui Plot3DUi<'ui>,
1410    label: Cow<'ui, str>,
1411    tex_ref: sys::ImTextureRef_c,
1412    center: [f32; 3],
1413    axis_u: [f32; 3],
1414    axis_v: [f32; 3],
1415    uv0: [f32; 2],
1416    uv1: [f32; 2],
1417    tint: [f32; 4],
1418    flags: Image3DFlags,
1419    item_flags: Item3DFlags,
1420    style: Plot3DItemStyle,
1421}
1422
1423impl<'ui> Image3DByAxesBuilder<'ui> {
1424    pub fn uv(mut self, uv0: [f32; 2], uv1: [f32; 2]) -> Self {
1425        self.uv0 = uv0;
1426        self.uv1 = uv1;
1427        self
1428    }
1429    pub fn tint(mut self, col: [f32; 4]) -> Self {
1430        self.tint = col;
1431        self
1432    }
1433    pub fn flags(mut self, flags: Image3DFlags) -> Self {
1434        self.flags = flags;
1435        self
1436    }
1437    pub fn plot(self) {
1438        let label = self.label.as_ref();
1439        let label = if label.contains('\0') { "image" } else { label };
1440        dear_imgui_rs::with_scratch_txt(label, |label_ptr| unsafe {
1441            debug_before_plot();
1442            let spec = plot3d_spec_with_style(
1443                self.style,
1444                self.flags.bits() | self.item_flags.bits(),
1445                0,
1446                IMPLOT3D_AUTO,
1447            );
1448            sys::ImPlot3D_PlotImage_Vec2(
1449                label_ptr,
1450                self.tex_ref,
1451                sys::ImPlot3DPoint_c {
1452                    x: self.center[0] as f64,
1453                    y: self.center[1] as f64,
1454                    z: self.center[2] as f64,
1455                },
1456                sys::ImPlot3DPoint_c {
1457                    x: self.axis_u[0] as f64,
1458                    y: self.axis_u[1] as f64,
1459                    z: self.axis_u[2] as f64,
1460                },
1461                sys::ImPlot3DPoint_c {
1462                    x: self.axis_v[0] as f64,
1463                    y: self.axis_v[1] as f64,
1464                    z: self.axis_v[2] as f64,
1465                },
1466                imvec2(self.uv0[0], self.uv0[1]),
1467                imvec2(self.uv1[0], self.uv1[1]),
1468                imvec4(self.tint[0], self.tint[1], self.tint[2], self.tint[3]),
1469                spec,
1470            );
1471        })
1472    }
1473}
1474
1475/// Image by corners builder
1476pub struct Image3DByCornersBuilder<'ui> {
1477    _ui: &'ui Plot3DUi<'ui>,
1478    label: Cow<'ui, str>,
1479    tex_ref: sys::ImTextureRef_c,
1480    p0: [f32; 3],
1481    p1: [f32; 3],
1482    p2: [f32; 3],
1483    p3: [f32; 3],
1484    uv0: [f32; 2],
1485    uv1: [f32; 2],
1486    uv2: [f32; 2],
1487    uv3: [f32; 2],
1488    tint: [f32; 4],
1489    flags: Image3DFlags,
1490    item_flags: Item3DFlags,
1491    style: Plot3DItemStyle,
1492}
1493
1494impl<'ui> Image3DByCornersBuilder<'ui> {
1495    pub fn uvs(mut self, uv0: [f32; 2], uv1: [f32; 2], uv2: [f32; 2], uv3: [f32; 2]) -> Self {
1496        self.uv0 = uv0;
1497        self.uv1 = uv1;
1498        self.uv2 = uv2;
1499        self.uv3 = uv3;
1500        self
1501    }
1502    pub fn tint(mut self, col: [f32; 4]) -> Self {
1503        self.tint = col;
1504        self
1505    }
1506    pub fn flags(mut self, flags: Image3DFlags) -> Self {
1507        self.flags = flags;
1508        self
1509    }
1510    pub fn plot(self) {
1511        let label = self.label.as_ref();
1512        let label = if label.contains('\0') { "image" } else { label };
1513        dear_imgui_rs::with_scratch_txt(label, |label_ptr| unsafe {
1514            debug_before_plot();
1515            let spec = plot3d_spec_with_style(
1516                self.style,
1517                self.flags.bits() | self.item_flags.bits(),
1518                0,
1519                IMPLOT3D_AUTO,
1520            );
1521            sys::ImPlot3D_PlotImage_Plot3DPoint(
1522                label_ptr,
1523                self.tex_ref,
1524                sys::ImPlot3DPoint_c {
1525                    x: self.p0[0] as f64,
1526                    y: self.p0[1] as f64,
1527                    z: self.p0[2] as f64,
1528                },
1529                sys::ImPlot3DPoint_c {
1530                    x: self.p1[0] as f64,
1531                    y: self.p1[1] as f64,
1532                    z: self.p1[2] as f64,
1533                },
1534                sys::ImPlot3DPoint_c {
1535                    x: self.p2[0] as f64,
1536                    y: self.p2[1] as f64,
1537                    z: self.p2[2] as f64,
1538                },
1539                sys::ImPlot3DPoint_c {
1540                    x: self.p3[0] as f64,
1541                    y: self.p3[1] as f64,
1542                    z: self.p3[2] as f64,
1543                },
1544                imvec2(self.uv0[0], self.uv0[1]),
1545                imvec2(self.uv1[0], self.uv1[1]),
1546                imvec2(self.uv2[0], self.uv2[1]),
1547                imvec2(self.uv3[0], self.uv3[1]),
1548                imvec4(self.tint[0], self.tint[1], self.tint[2], self.tint[3]),
1549                spec,
1550            );
1551        })
1552    }
1553}
1554
1555impl<'ui> Plot3DUi<'ui> {
1556    /// Image oriented by center and axes
1557    pub fn image_by_axes<T: Into<TextureRef>>(
1558        &'ui self,
1559        label: impl Into<Cow<'ui, str>>,
1560        tex: T,
1561        center: [f32; 3],
1562        axis_u: [f32; 3],
1563        axis_v: [f32; 3],
1564    ) -> Image3DByAxesBuilder<'ui> {
1565        let tr = tex.into().raw();
1566        let tex_ref = sys::ImTextureRef_c {
1567            _TexData: tr._TexData as *mut sys::ImTextureData,
1568            _TexID: tr._TexID as sys::ImTextureID,
1569        };
1570        debug_before_plot();
1571        Image3DByAxesBuilder {
1572            _ui: self,
1573            label: label.into(),
1574            tex_ref,
1575            center,
1576            axis_u,
1577            axis_v,
1578            uv0: [0.0, 0.0],
1579            uv1: [1.0, 1.0],
1580            tint: [1.0, 1.0, 1.0, 1.0],
1581            flags: Image3DFlags::NONE,
1582            item_flags: Item3DFlags::NONE,
1583            style: Plot3DItemStyle::default(),
1584        }
1585    }
1586
1587    /// Image by 4 corner points (p0..p3)
1588    pub fn image_by_corners<T: Into<TextureRef>>(
1589        &'ui self,
1590        label: impl Into<Cow<'ui, str>>,
1591        tex: T,
1592        p0: [f32; 3],
1593        p1: [f32; 3],
1594        p2: [f32; 3],
1595        p3: [f32; 3],
1596    ) -> Image3DByCornersBuilder<'ui> {
1597        let tr = tex.into().raw();
1598        let tex_ref = sys::ImTextureRef_c {
1599            _TexData: tr._TexData as *mut sys::ImTextureData,
1600            _TexID: tr._TexID as sys::ImTextureID,
1601        };
1602        debug_before_plot();
1603        Image3DByCornersBuilder {
1604            _ui: self,
1605            label: label.into(),
1606            tex_ref,
1607            p0,
1608            p1,
1609            p2,
1610            p3,
1611            uv0: [0.0, 0.0],
1612            uv1: [1.0, 0.0],
1613            uv2: [1.0, 1.0],
1614            uv3: [0.0, 1.0],
1615            tint: [1.0, 1.0, 1.0, 1.0],
1616            flags: Image3DFlags::NONE,
1617            item_flags: Item3DFlags::NONE,
1618            style: Plot3DItemStyle::default(),
1619        }
1620    }
1621}
1622
1623/// Axis helpers
1624impl<'ui> Plot3DUi<'ui> {
1625    pub fn setup_axes(
1626        &self,
1627        x_label: &str,
1628        y_label: &str,
1629        z_label: &str,
1630        x_flags: Axis3DFlags,
1631        y_flags: Axis3DFlags,
1632        z_flags: Axis3DFlags,
1633    ) {
1634        debug_before_setup();
1635        if x_label.contains('\0') || y_label.contains('\0') || z_label.contains('\0') {
1636            return;
1637        }
1638        dear_imgui_rs::with_scratch_txt_three(
1639            x_label,
1640            y_label,
1641            z_label,
1642            |x_ptr, y_ptr, z_ptr| unsafe {
1643                sys::ImPlot3D_SetupAxes(
1644                    x_ptr,
1645                    y_ptr,
1646                    z_ptr,
1647                    x_flags.bits() as i32,
1648                    y_flags.bits() as i32,
1649                    z_flags.bits() as i32,
1650                )
1651            },
1652        )
1653    }
1654
1655    pub fn setup_axis(&self, axis: Axis3D, label: &str, flags: Axis3DFlags) {
1656        debug_before_setup();
1657        if label.contains('\0') {
1658            return;
1659        }
1660        dear_imgui_rs::with_scratch_txt(label, |ptr| unsafe {
1661            sys::ImPlot3D_SetupAxis(axis as i32, ptr, flags.bits() as i32)
1662        })
1663    }
1664
1665    pub fn setup_axis_limits(&self, axis: Axis3D, min: f64, max: f64, cond: Plot3DCond) {
1666        debug_before_setup();
1667        unsafe { sys::ImPlot3D_SetupAxisLimits(axis as i32, min, max, cond as i32) }
1668    }
1669
1670    pub fn setup_axes_limits(
1671        &self,
1672        x_min: f64,
1673        x_max: f64,
1674        y_min: f64,
1675        y_max: f64,
1676        z_min: f64,
1677        z_max: f64,
1678        cond: Plot3DCond,
1679    ) {
1680        debug_before_setup();
1681        unsafe {
1682            sys::ImPlot3D_SetupAxesLimits(x_min, x_max, y_min, y_max, z_min, z_max, cond as i32)
1683        }
1684    }
1685
1686    pub fn setup_axis_limits_constraints(&self, axis: Axis3D, v_min: f64, v_max: f64) {
1687        debug_before_setup();
1688        unsafe { sys::ImPlot3D_SetupAxisLimitsConstraints(axis as i32, v_min, v_max) }
1689    }
1690
1691    pub fn setup_axis_zoom_constraints(&self, axis: Axis3D, z_min: f64, z_max: f64) {
1692        debug_before_setup();
1693        unsafe { sys::ImPlot3D_SetupAxisZoomConstraints(axis as i32, z_min, z_max) }
1694    }
1695
1696    /// Setup axis ticks using explicit positions and optional labels.
1697    ///
1698    /// If `labels` is provided, it must have the same length as `values`.
1699    pub fn setup_axis_ticks_values(
1700        &self,
1701        axis: Axis3D,
1702        values: &[f64],
1703        labels: Option<&[&str]>,
1704        keep_default: bool,
1705    ) {
1706        debug_before_setup();
1707        let Some(n_ticks) = len_i32(values.len()) else {
1708            return;
1709        };
1710        if let Some(lbls) = labels {
1711            if lbls.len() != values.len() {
1712                return;
1713            }
1714            let cleaned: Vec<&str> = lbls
1715                .iter()
1716                .map(|&s| if s.contains('\0') { "" } else { s })
1717                .collect();
1718            dear_imgui_rs::with_scratch_txt_slice(&cleaned, |ptrs| unsafe {
1719                sys::ImPlot3D_SetupAxisTicks_doublePtr(
1720                    axis as i32,
1721                    values.as_ptr(),
1722                    n_ticks,
1723                    ptrs.as_ptr(),
1724                    keep_default,
1725                )
1726            });
1727        } else {
1728            unsafe {
1729                sys::ImPlot3D_SetupAxisTicks_doublePtr(
1730                    axis as i32,
1731                    values.as_ptr(),
1732                    n_ticks,
1733                    std::ptr::null(),
1734                    keep_default,
1735                )
1736            };
1737        }
1738    }
1739
1740    pub fn setup_axis_ticks_range(
1741        &self,
1742        axis: Axis3D,
1743        v_min: f64,
1744        v_max: f64,
1745        n_ticks: i32,
1746        labels: Option<&[&str]>,
1747        keep_default: bool,
1748    ) {
1749        debug_before_setup();
1750        if let Some(lbls) = labels {
1751            let cleaned: Vec<&str> = lbls
1752                .iter()
1753                .map(|&s| if s.contains('\0') { "" } else { s })
1754                .collect();
1755            dear_imgui_rs::with_scratch_txt_slice(&cleaned, |ptrs| unsafe {
1756                sys::ImPlot3D_SetupAxisTicks_double(
1757                    axis as i32,
1758                    v_min,
1759                    v_max,
1760                    n_ticks,
1761                    ptrs.as_ptr(),
1762                    keep_default,
1763                )
1764            });
1765        } else {
1766            unsafe {
1767                sys::ImPlot3D_SetupAxisTicks_double(
1768                    axis as i32,
1769                    v_min,
1770                    v_max,
1771                    n_ticks,
1772                    std::ptr::null(),
1773                    keep_default,
1774                )
1775            };
1776        }
1777    }
1778
1779    pub fn setup_box_scale(&self, x: f32, y: f32, z: f32) {
1780        debug_before_setup();
1781        unsafe { sys::ImPlot3D_SetupBoxScale(x as f64, y as f64, z as f64) }
1782    }
1783
1784    pub fn setup_box_rotation(
1785        &self,
1786        elevation: f32,
1787        azimuth: f32,
1788        animate: bool,
1789        cond: Plot3DCond,
1790    ) {
1791        debug_before_setup();
1792        unsafe {
1793            sys::ImPlot3D_SetupBoxRotation_double(
1794                elevation as f64,
1795                azimuth as f64,
1796                animate,
1797                cond as i32,
1798            )
1799        }
1800    }
1801
1802    pub fn setup_box_initial_rotation(&self, elevation: f32, azimuth: f32) {
1803        debug_before_setup();
1804        unsafe { sys::ImPlot3D_SetupBoxInitialRotation_double(elevation as f64, azimuth as f64) }
1805    }
1806
1807    pub fn plot_text(&self, text: &str, x: f32, y: f32, z: f32, angle: f32, pix_offset: [f32; 2]) {
1808        if text.contains('\0') {
1809            return;
1810        }
1811        dear_imgui_rs::with_scratch_txt(text, |text_ptr| unsafe {
1812            debug_before_plot();
1813            sys::ImPlot3D_PlotText(
1814                text_ptr,
1815                x as f64,
1816                y as f64,
1817                z as f64,
1818                angle as f64,
1819                imvec2(pix_offset[0], pix_offset[1]),
1820            )
1821        })
1822    }
1823
1824    pub fn plot_to_pixels(&self, point: [f32; 3]) -> [f32; 2] {
1825        unsafe {
1826            let out = compat_ffi::ImPlot3D_PlotToPixels_double(
1827                point[0] as f64,
1828                point[1] as f64,
1829                point[2] as f64,
1830            );
1831            [out.x, out.y]
1832        }
1833    }
1834
1835    pub fn get_plot_draw_list(&self) -> *mut sys::ImDrawList {
1836        unsafe { sys::ImPlot3D_GetPlotDrawList() }
1837    }
1838
1839    pub fn get_frame_pos(&self) -> [f32; 2] {
1840        unsafe {
1841            let out = compat_ffi::ImPlot3D_GetPlotRectPos();
1842            [out.x, out.y]
1843        }
1844    }
1845
1846    pub fn get_frame_size(&self) -> [f32; 2] {
1847        unsafe {
1848            let out = compat_ffi::ImPlot3D_GetPlotRectSize();
1849            [out.x, out.y]
1850        }
1851    }
1852}
1853
1854/// Mesh plot builder
1855pub struct Mesh3DBuilder<'ui> {
1856    _ui: &'ui Plot3DUi<'ui>,
1857    label: Cow<'ui, str>,
1858    vertices: &'ui [[f32; 3]],
1859    indices: &'ui [u32],
1860    flags: Mesh3DFlags,
1861    item_flags: Item3DFlags,
1862    style: Plot3DItemStyle,
1863}
1864
1865impl<'ui> Mesh3DBuilder<'ui> {
1866    pub fn flags(mut self, flags: Mesh3DFlags) -> Self {
1867        self.flags = flags;
1868        self
1869    }
1870    pub fn plot(self) {
1871        let Some(vtx_count) = len_i32(self.vertices.len()) else {
1872            return;
1873        };
1874        let Some(idx_count) = len_i32(self.indices.len()) else {
1875            return;
1876        };
1877        let mut xs = Vec::with_capacity(self.vertices.len());
1878        let mut ys = Vec::with_capacity(self.vertices.len());
1879        let mut zs = Vec::with_capacity(self.vertices.len());
1880        for [x, y, z] in self.vertices.iter().copied() {
1881            xs.push(x);
1882            ys.push(y);
1883            zs.push(z);
1884        }
1885
1886        let label = self.label.as_ref();
1887        let label = if label.contains('\0') { "mesh" } else { label };
1888        dear_imgui_rs::with_scratch_txt(label, |label_ptr| unsafe {
1889            debug_before_plot();
1890            let spec = plot3d_spec_with_style(
1891                self.style,
1892                self.flags.bits() | self.item_flags.bits(),
1893                0,
1894                std::mem::size_of::<f32>() as i32,
1895            );
1896            sys::ImPlot3D_PlotMesh_FloatPtr(
1897                label_ptr,
1898                xs.as_ptr(),
1899                ys.as_ptr(),
1900                zs.as_ptr(),
1901                self.indices.as_ptr(),
1902                vtx_count,
1903                idx_count,
1904                spec,
1905            );
1906        })
1907    }
1908}
1909
1910impl<'ui> Plot3DUi<'ui> {
1911    /// Start a mesh plot from vertices (x,y,z) and triangle indices
1912    pub fn mesh(
1913        &'ui self,
1914        label: impl Into<Cow<'ui, str>>,
1915        vertices: &'ui [[f32; 3]],
1916        indices: &'ui [u32],
1917    ) -> Mesh3DBuilder<'ui> {
1918        Mesh3DBuilder {
1919            _ui: self,
1920            label: label.into(),
1921            vertices,
1922            indices,
1923            flags: Mesh3DFlags::NONE,
1924            item_flags: Item3DFlags::NONE,
1925            style: Plot3DItemStyle::default(),
1926        }
1927    }
1928}
1929
1930#[cfg(test)]
1931mod tests {
1932    use super::sys;
1933    use std::mem::{align_of, size_of};
1934
1935    #[test]
1936    fn ffi_layout_implot3d_point_is_3_f64() {
1937        assert_eq!(size_of::<sys::ImPlot3DPoint>(), 3 * size_of::<f64>());
1938        assert_eq!(align_of::<sys::ImPlot3DPoint>(), align_of::<f64>());
1939    }
1940}