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