1 | use super::{ProjectionMatrix, ProjectionMatrixBuilder}; |
2 | use crate::coord::ranged1d::Ranged; |
3 | use crate::coord::CoordTranslate; |
4 | use plotters_backend::BackendCoord; |
5 | |
6 | use std::ops::Range; |
7 | |
8 | /// A 3D cartesian coordinate system |
9 | #[derive(Clone)] |
10 | pub struct Cartesian3d<X: Ranged, Y: Ranged, Z: Ranged> { |
11 | pub(crate) logic_x: X, |
12 | pub(crate) logic_y: Y, |
13 | pub(crate) logic_z: Z, |
14 | coord_size: (i32, i32, i32), |
15 | projection: ProjectionMatrix, |
16 | } |
17 | |
18 | impl<X: Ranged, Y: Ranged, Z: Ranged> Cartesian3d<X, Y, Z> { |
19 | fn compute_default_size(actual_x: Range<i32>, actual_y: Range<i32>) -> i32 { |
20 | (actual_x.end - actual_x.start).min(actual_y.end - actual_y.start) * 4 / 5 |
21 | } |
22 | fn create_projection<F: FnOnce(ProjectionMatrixBuilder) -> ProjectionMatrix>( |
23 | actual_x: Range<i32>, |
24 | actual_y: Range<i32>, |
25 | coord_size: (i32, i32, i32), |
26 | f: F, |
27 | ) -> ProjectionMatrix { |
28 | let center_3d = (coord_size.0 / 2, coord_size.1 / 2, coord_size.2 / 2); |
29 | let center_2d = ( |
30 | (actual_x.end + actual_x.start) / 2, |
31 | (actual_y.end + actual_y.start) / 2, |
32 | ); |
33 | let mut pb = ProjectionMatrixBuilder::new(); |
34 | pb.set_pivot(center_3d, center_2d); |
35 | f(pb) |
36 | } |
37 | /// Creates a Cartesian3d object with the given projection. |
38 | pub fn with_projection< |
39 | SX: Into<X>, |
40 | SY: Into<Y>, |
41 | SZ: Into<Z>, |
42 | F: FnOnce(ProjectionMatrixBuilder) -> ProjectionMatrix, |
43 | >( |
44 | logic_x: SX, |
45 | logic_y: SY, |
46 | logic_z: SZ, |
47 | (actual_x, actual_y): (Range<i32>, Range<i32>), |
48 | build_projection_matrix: F, |
49 | ) -> Self { |
50 | let default_size = Self::compute_default_size(actual_x.clone(), actual_y.clone()); |
51 | let coord_size = (default_size, default_size, default_size); |
52 | Self { |
53 | logic_x: logic_x.into(), |
54 | logic_y: logic_y.into(), |
55 | logic_z: logic_z.into(), |
56 | coord_size, |
57 | projection: Self::create_projection( |
58 | actual_x, |
59 | actual_y, |
60 | coord_size, |
61 | build_projection_matrix, |
62 | ), |
63 | } |
64 | } |
65 | |
66 | /// Sets the pixel sizes and projections according to the given ranges. |
67 | pub fn set_coord_pixel_range( |
68 | &mut self, |
69 | actual_x: Range<i32>, |
70 | actual_y: Range<i32>, |
71 | coord_size: (i32, i32, i32), |
72 | ) -> &mut Self { |
73 | self.coord_size = coord_size; |
74 | self.projection = |
75 | Self::create_projection(actual_x, actual_y, coord_size, |pb| pb.into_matrix()); |
76 | self |
77 | } |
78 | |
79 | /// Set the projection matrix |
80 | pub fn set_projection<F: FnOnce(ProjectionMatrixBuilder) -> ProjectionMatrix>( |
81 | &mut self, |
82 | actual_x: Range<i32>, |
83 | actual_y: Range<i32>, |
84 | f: F, |
85 | ) -> &mut Self { |
86 | self.projection = Self::create_projection(actual_x, actual_y, self.coord_size, f); |
87 | self |
88 | } |
89 | |
90 | /// Create a new coordinate |
91 | pub fn new<SX: Into<X>, SY: Into<Y>, SZ: Into<Z>>( |
92 | logic_x: SX, |
93 | logic_y: SY, |
94 | logic_z: SZ, |
95 | (actual_x, actual_y): (Range<i32>, Range<i32>), |
96 | ) -> Self { |
97 | Self::with_projection(logic_x, logic_y, logic_z, (actual_x, actual_y), |pb| { |
98 | pb.into_matrix() |
99 | }) |
100 | } |
101 | /// Get the projection matrix |
102 | pub fn projection(&self) -> &ProjectionMatrix { |
103 | &self.projection |
104 | } |
105 | |
106 | /// Do not project, only transform the guest coordinate system |
107 | pub fn map_3d(&self, x: &X::ValueType, y: &Y::ValueType, z: &Z::ValueType) -> (i32, i32, i32) { |
108 | ( |
109 | self.logic_x.map(x, (0, self.coord_size.0)), |
110 | self.logic_y.map(y, (0, self.coord_size.1)), |
111 | self.logic_z.map(z, (0, self.coord_size.2)), |
112 | ) |
113 | } |
114 | |
115 | /// Get the depth of the projection |
116 | pub fn projected_depth(&self, x: &X::ValueType, y: &Y::ValueType, z: &Z::ValueType) -> i32 { |
117 | self.projection.projected_depth(self.map_3d(x, y, z)) |
118 | } |
119 | } |
120 | |
121 | impl<X: Ranged, Y: Ranged, Z: Ranged> CoordTranslate for Cartesian3d<X, Y, Z> { |
122 | type From = (X::ValueType, Y::ValueType, Z::ValueType); |
123 | fn translate(&self, coord: &Self::From) -> BackendCoord { |
124 | let pixel_coord_3d = self.map_3d(&coord.0, &coord.1, &coord.2); |
125 | self.projection * pixel_coord_3d |
126 | } |
127 | |
128 | fn depth(&self, coord: &Self::From) -> i32 { |
129 | self.projected_depth(&coord.0, &coord.1, &coord.2) |
130 | } |
131 | } |
132 | |