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 /// 
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}