Skip to main content

dear_implot3d/
lib.rs

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