omf/
block_model.rs

1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3
4use crate::{
5    Array, Grid3, Location, Orient3,
6    array::Constraint,
7    array_type,
8    validate::{Validate, Validator},
9};
10
11/// Block model geometry with optional sub-blocks.
12///
13/// First, the `orient` field defines the position and orientation of a (U, V, W) space relative
14/// to the project, which could be just an offset or a full rotation as well. Then the `grid`
15/// field defines the size and number of parent blocks aligned with that space and starting at
16/// (0, 0, 0). [Sub-blocks](crate::Subblocks) can then optionally be added inside those parent
17/// blocks using a variety of layouts.
18///
19/// While sub-blocks are supported on tensor grids it isn't a common arrangement and many
20/// applications won't load them.
21///
22/// ### Attribute Locations
23///
24/// - [`Vertices`](crate::Location::Vertices) puts attribute values on the corners of the
25///   parent blocks. If the block count is $(N_0, N_1, N_2)$ then there must be
26///   $(N_0 + 1) · (N_1 + 1) · (N_2 + 1)$ values. Ordering increases U first, then V, then W.
27///
28/// - [`Blocks`](crate::Location::Primitives) puts attribute values on the centroids of the
29///   parent block. If the block count is $(N_0, N_1, N_2)$ then there must be
30///   $N_0 · N_1 · N_2$ values. Ordering increases U first, then V, then W.
31///
32/// - [`Subblocks`](crate::Location::Subblocks) puts attribute values on sub-block centroids.
33///   The number and values and their ordering matches the `parents` and `corners` arrays.
34///
35///   To have attribute values on undivided parent blocks in this mode there must be a sub-block
36///   that covers the whole parent block.
37#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
38pub struct BlockModel {
39    /// Orientation of the block model.
40    pub orient: Orient3,
41    /// Block sizes.
42    pub grid: Grid3,
43    /// Optional sub-blocks, which can be regular or free-form divisions of the parent blocks.
44    #[serde(default, skip_serializing_if = "Option::is_none")]
45    pub subblocks: Option<Subblocks>,
46}
47
48impl BlockModel {
49    pub fn new(orient: Orient3, grid: Grid3) -> Self {
50        Self {
51            orient,
52            grid,
53            subblocks: None,
54        }
55    }
56
57    pub fn with_subblocks(orient: Orient3, grid: Grid3, subblocks: Subblocks) -> Self {
58        Self {
59            orient,
60            grid,
61            subblocks: Some(subblocks),
62        }
63    }
64
65    pub fn with_regular_subblocks(
66        orient: Orient3,
67        grid: Grid3,
68        subblock_count: [u32; 3],
69        subblocks: Array<array_type::RegularSubblock>,
70        mode: Option<SubblockMode>,
71    ) -> Self {
72        Self {
73            orient,
74            grid,
75            subblocks: Some(Subblocks::Regular {
76                count: subblock_count,
77                subblocks,
78                mode,
79            }),
80        }
81    }
82
83    pub fn with_freeform_subblocks(
84        orient: Orient3,
85        grid: Grid3,
86        subblocks: Array<array_type::FreeformSubblock>,
87    ) -> Self {
88        Self {
89            orient,
90            grid,
91            subblocks: Some(Subblocks::Freeform { subblocks }),
92        }
93    }
94
95    /// Returns true if the model has sub-blocks.
96    pub fn has_subblocks(&self) -> bool {
97        self.subblocks.is_some()
98    }
99
100    pub fn location_len(&self, location: Location) -> Option<u64> {
101        match (&self.subblocks, location) {
102            (_, Location::Vertices) => Some(self.grid.flat_corner_count()),
103            (_, Location::Primitives) => Some(self.grid.flat_count()),
104            (Some(s), Location::Subblocks) => Some(s.len()),
105            _ => None,
106        }
107    }
108}
109
110/// Stores sub-blocks of a block model.
111#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
112#[serde(tag = "type")]
113pub enum Subblocks {
114    /// Divide each parent block into a regular grid of `count` cells. Sub-blocks each covers
115    /// a non-overlapping cuboid subset of that grid.
116    ///
117    /// Sub-blocks are described by the `parents` and `corners` arrays. Those arrays must be the
118    /// same length and matching rows in each describe the same sub-block. Each row in `parents`
119    /// is an IJK index on the block model grid. Each row of
120    /// `corners` is $(i_{min}, j_{min}, k_{min}, i_{max}, j_{max}, k_{max})$, all integers, that
121    /// refer to the *vertices* of the sub-block grid within the parent block. For example:
122    ///
123    /// - A block with minimum size in the corner of the parent block would be (0, 0, 0, 1, 1, 1).
124    ///
125    /// - If the `subblock_count` is (5, 5, 3) then a sub-block covering the whole parent would
126    ///   be (0, 0, 0, 5, 5, 3).
127    ///
128    /// Sub-blocks must stay within their parent, must have a non-zero size in all directions, and
129    /// should not overlap. Further restrictions can be applied by the `mode` field, see
130    /// [`SubblockMode`](crate::SubblockMode) for details.
131    ///
132    /// ![Example of regular sub-blocks](../images/subblocks_regular.svg "Regular sub-bloks")
133    Regular {
134        /// The sub-block grid size.
135        ///
136        /// Must be greater than zero in all directions. If `mode` is octree then these must also
137        /// be powers of two but they don't have to be equal.
138        count: [u32; 3],
139        /// Array with `RegularSubblock` type storing the sub-block parent indices and corners
140        /// relative to the sub-block grid within the parent.
141        subblocks: Array<array_type::RegularSubblock>,
142        /// If present this further restricts the sub-block layout.
143        mode: Option<SubblockMode>,
144    },
145    /// Divide each parent block into any number and arrangement of non-overlapping cuboid regions.
146    ///
147    /// Sub-blocks are described by the `parents` and `corners` arrays. Each row in `parents` is
148    /// an IJK index on the block model grid. Each row of `corners` is
149    /// $(i_{min}, j_{min}, k_{min}, i_{max}, j_{max}, k_{max})$ in floating-point and relative
150    /// to the parent block, running from 0.0 to 1.0 across the parent. For example:
151    ///
152    /// - A sub-block covering the whole parent will be (0.0, 0.0, 0.0, 1.0, 1.0, 1.0)
153    ///   no matter the size of the parent.
154    ///
155    /// - A sub-block covering the bottom third of the parent block would be
156    ///   (0.0, 0.0, 0.0, 1.0, 1.0, 0.3333) and one covering the top two-thirds would be
157    ///   (0.0, 0.0, 0.3333, 1.0, 1.0, 1.0), again no matter the size of the parent.
158    ///
159    /// Sub-blocks must stay within their parent, must have a non-zero size in all directions,
160    /// and shouldn't overlap.
161    Freeform {
162        /// Array with `FreeformSubblock` type storing the sub-block parent indices and corners
163        /// relative to the parent.
164        subblocks: Array<array_type::FreeformSubblock>,
165    },
166}
167
168impl Subblocks {
169    /// The number of sub-blocks.
170    pub fn len(&self) -> u64 {
171        match self {
172            Self::Regular { subblocks, .. } => subblocks.item_count(),
173            Self::Freeform { subblocks, .. } => subblocks.item_count(),
174        }
175    }
176
177    /// True if there are no sub-blocks.
178    pub fn is_empty(&self) -> bool {
179        self.len() == 0
180    }
181
182    /// Returns the optional sub-block mode.
183    ///
184    /// Currently this will always be `None` for free-form sub-blocks.
185    pub fn mode(&self) -> Option<SubblockMode> {
186        match self {
187            Subblocks::Regular { mode, .. } => *mode,
188            _ => None,
189        }
190    }
191
192    fn validate(&mut self, block_count: [u32; 3], val: &mut Validator) {
193        match self {
194            Subblocks::Regular {
195                count,
196                subblocks,
197                mode,
198            } => {
199                val.enter("Subblocks::Regular")
200                    .above_zero_seq(*count, "count")
201                    .subblock_mode_and_count(*mode, *count)
202                    .array(
203                        subblocks,
204                        Constraint::RegularSubblock {
205                            block_count,
206                            subblock_count: *count,
207                            mode: *mode,
208                        },
209                        "subblocks",
210                    );
211            }
212            Subblocks::Freeform { subblocks } => {
213                val.enter("Subblocks::Freeform").array(
214                    subblocks,
215                    Constraint::FreeformSubblock { block_count },
216                    "subblocks",
217                );
218            }
219        }
220    }
221}
222
223/// A optional mode for regular sub-blocks.
224#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
225pub enum SubblockMode {
226    /// Sub-blocks form a octree-like inside the parent block.
227    ///
228    /// To form this structure, cut the parent block in half in all directions to create
229    /// eight child blocks. Repeat that cut for some or all of those children, and continue
230    /// doing that until the limit on sub-block count is reached or until the sub-blocks
231    /// accurately model the inputs.
232    ///
233    /// The sub-block count must be a power of two in each direction. This isn't strictly an
234    /// octree because the sub-block count doesn't have to be the *same* in all directions.
235    /// For example you can have count (16, 16, 2) and blocks will stop dividing the the W
236    /// direction after the first split.
237    Octree,
238    /// Parent blocks are fully divided or not divided at all.
239    ///
240    /// Applications reading this mode may choose to merge sub-blocks with matching attributes
241    /// to reduce the overall number of them.
242    Full,
243}
244
245impl Validate for BlockModel {
246    fn validate_inner(&mut self, val: &mut Validator) {
247        let mut v = val
248            .enter("BlockModel")
249            .obj(&mut self.orient)
250            .obj(&mut self.grid);
251        if let Some(subblocks) = &mut self.subblocks {
252            subblocks.validate(self.grid.count(), &mut v);
253        }
254    }
255}