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