bentopy 0.2.0a10__cp313-cp313-manylinux_2_34_x86_64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. bentopy-0.2.0a10.data/scripts/bentopy-init +0 -0
  2. bentopy-0.2.0a10.data/scripts/bentopy-pack +0 -0
  3. bentopy-0.2.0a10.data/scripts/bentopy-render +0 -0
  4. bentopy-0.2.0a10.data/scripts/bentopy-solvate +0 -0
  5. bentopy-0.2.0a10.dist-info/METADATA +358 -0
  6. bentopy-0.2.0a10.dist-info/RECORD +58 -0
  7. bentopy-0.2.0a10.dist-info/WHEEL +5 -0
  8. bentopy-0.2.0a10.dist-info/entry_points.txt +4 -0
  9. bentopy-0.2.0a10.dist-info/licenses/LICENSE.txt +13 -0
  10. bentopy-0.2.0a10.dist-info/top_level.txt +8 -0
  11. check/check.py +128 -0
  12. core/config/bent/lexer.rs +338 -0
  13. core/config/bent/parser.rs +1180 -0
  14. core/config/bent/writer.rs +205 -0
  15. core/config/bent.rs +149 -0
  16. core/config/compartment_combinations.rs +300 -0
  17. core/config/legacy.rs +768 -0
  18. core/config.rs +362 -0
  19. core/mod.rs +4 -0
  20. core/placement.rs +100 -0
  21. core/utilities.rs +1 -0
  22. core/version.rs +32 -0
  23. init/example.bent +74 -0
  24. init/main.rs +235 -0
  25. mask/config.py +153 -0
  26. mask/mask.py +308 -0
  27. mask/utilities.py +38 -0
  28. merge/merge.py +175 -0
  29. pack/args.rs +77 -0
  30. pack/main.rs +121 -0
  31. pack/mask.rs +940 -0
  32. pack/session.rs +176 -0
  33. pack/state/combinations.rs +31 -0
  34. pack/state/compartment.rs +44 -0
  35. pack/state/mask.rs +196 -0
  36. pack/state/pack.rs +187 -0
  37. pack/state/segment.rs +72 -0
  38. pack/state/space.rs +98 -0
  39. pack/state.rs +440 -0
  40. pack/structure.rs +185 -0
  41. pack/voxelize.rs +85 -0
  42. render/args.rs +109 -0
  43. render/limits.rs +73 -0
  44. render/main.rs +12 -0
  45. render/render.rs +393 -0
  46. render/structure.rs +264 -0
  47. solvate/args.rs +324 -0
  48. solvate/convert.rs +25 -0
  49. solvate/cookies.rs +185 -0
  50. solvate/main.rs +177 -0
  51. solvate/placement.rs +380 -0
  52. solvate/solvate.rs +244 -0
  53. solvate/structure.rs +160 -0
  54. solvate/substitute.rs +113 -0
  55. solvate/water/martini.rs +409 -0
  56. solvate/water/models.rs +150 -0
  57. solvate/water/tip3p.rs +658 -0
  58. solvate/water.rs +115 -0
solvate/args.rs ADDED
@@ -0,0 +1,324 @@
1
+ use std::path::PathBuf;
2
+ use std::str::FromStr;
3
+
4
+ use clap::{Parser, ValueEnum};
5
+ use eightyseven::structure::ResName;
6
+
7
+ /// Solvate.
8
+ #[derive(Debug, Parser)]
9
+ #[command(about, version = bentopy::core::version::VERSION)]
10
+ pub struct Args {
11
+ /// Structure input path.
12
+ #[arg(short, long)]
13
+ pub input: PathBuf,
14
+ /// Output path.
15
+ #[arg(short, long)]
16
+ pub output: PathBuf,
17
+
18
+ /// Lowest allowable distance between solvent and structure beads (nm).
19
+ ///
20
+ /// This is the minimum allowable center-to-center distance when checking for a collision
21
+ /// between a solvent bead and a structure bead.
22
+ ///
23
+ /// For the solvent-solvent cutoff distance, see `--solvent-cutoff`.
24
+ ///
25
+ /// For Martini solvation, the default cutoff is 0.43 nm. For atomistic solvation, the default
26
+ /// cutoff is 0.28.
27
+ #[arg(long)]
28
+ pub cutoff: Option<f32>,
29
+
30
+ /// Lowest allowable distance between solvent beads (nm).
31
+ ///
32
+ /// This is the minimum allowable center-to-center distance between solvent beads when cutting
33
+ /// the sides to fit in the specified output structure box.
34
+ ///
35
+ /// For Martini solvation, the default cutoff is 0.21 nm. For atomistic solvation, the default
36
+ /// cutoff is 0.21.
37
+ #[arg(long)]
38
+ pub solvent_cutoff: Option<f32>,
39
+
40
+ /// List of resnames to ignore when checking against structure-solvent collisions.
41
+ #[arg(long, value_delimiter = ',')]
42
+ pub ignore: Vec<ResName>,
43
+
44
+ /// The type of water written to the output file.
45
+ #[arg(long, value_enum, default_value_t)]
46
+ pub water_type: WaterType,
47
+
48
+ /// Center the structure in the new box.
49
+ ///
50
+ /// Note that this option only has an effect if the boundary mode is set to `grow`.
51
+ #[arg(short, long)]
52
+ pub center: bool,
53
+
54
+ /// Set how the boundaries of the box will be treated.
55
+ #[arg(short, long, value_enum, default_value_t)]
56
+ pub boundary_mode: BoundaryMode,
57
+
58
+ /// Set how periodic images of the input structure are considered.
59
+ ///
60
+ /// Note that only orthorhombic (cuboid) periodicity is considered, currently.
61
+ // TODO: Consider whether supporting other forms of periodicity is worthwhile.
62
+ #[arg(short, long, value_enum, default_value_t)]
63
+ pub periodic_mode: PeriodicMode,
64
+
65
+ /// Define substitutes for solvent residues, such as ions.
66
+ #[arg(short, long = "substitute")]
67
+ pub substitutes: Vec<SubstituteConfig>,
68
+
69
+ /// Set the charge to neutralize with additional ions.
70
+ ///
71
+ /// In essence, this is a shorthand for explicitly providing ions to compensate the charge as
72
+ /// substitutes. This can be helpful for automation purposes.
73
+ ///
74
+ /// By default, NA (positive) and CL (negative) ions are used, but a different
75
+ /// positive-negative substitution pair can be specified by prepending a colon followed by the
76
+ /// positive and negative substitute name separated by one comma.
77
+ ///
78
+ /// <charge>:<positive ion name>,<negative ion name>
79
+ ///
80
+ /// So, by default, some `<charge>` is interpreted as
81
+ ///
82
+ /// <charge>:NA,CL
83
+ #[arg(long, allow_hyphen_values = true)]
84
+ pub charge: Option<ChargeConfig>,
85
+
86
+ /// Combine substitutes with identical names into one block.
87
+ #[arg(long, default_value_t)]
88
+ pub no_combine_substitutes: bool,
89
+
90
+ /// Set whether and in what way substitutes are sorted.
91
+ #[arg(long, value_enum, default_value_t)]
92
+ pub sort_substitutes: SortBehavior,
93
+
94
+ /// Random number generator seed for solvent substitution, such as ion placement.
95
+ #[arg(long)]
96
+ pub seed: Option<u64>,
97
+
98
+ /// If the solvent template contains velocity information, write these velocities to the output
99
+ /// file.
100
+ #[arg(long, default_value_t)]
101
+ pub write_velocities: bool,
102
+
103
+ /// Append solvation topology lines to a path.
104
+ #[arg(short = 't', long)]
105
+ pub append_topol: Option<PathBuf>,
106
+
107
+ #[arg(long)]
108
+ pub no_write_parallel: bool,
109
+ /// The suggested number of atoms to format at once.
110
+ ///
111
+ /// Setting this to a larger value will allow more atoms to be formatted at once, at the cost
112
+ /// of higher memory consumption. Setting this to a smaller value will lower the memory
113
+ /// footprint at a possible cost of efficiency.
114
+ ///
115
+ /// Note that depending on the number of residues in the template box, the provided value may
116
+ /// be overshot by at most that number.
117
+ #[arg(long, default_value_t = 10000000)]
118
+ pub buffer_size: usize,
119
+ }
120
+
121
+ #[derive(Debug, Default, Clone, Copy, ValueEnum, PartialEq, Eq)]
122
+ #[clap(rename_all = "lowercase")]
123
+ pub enum WaterType {
124
+ #[default]
125
+ Martini,
126
+ Tip3P,
127
+ }
128
+
129
+ impl WaterType {
130
+ pub const fn default_cutoff(&self) -> f32 {
131
+ match self {
132
+ WaterType::Martini => 0.43,
133
+ WaterType::Tip3P => 0.28,
134
+ }
135
+ }
136
+
137
+ pub const fn default_solvent_cutoff(&self) -> f32 {
138
+ match self {
139
+ WaterType::Martini => 0.21,
140
+ WaterType::Tip3P => 0.21,
141
+ }
142
+ }
143
+ }
144
+
145
+ #[derive(Debug, Default, Clone, ValueEnum, PartialEq, Eq)]
146
+ pub enum BoundaryMode {
147
+ /// Cut at the structure box size and remove solvent residues that overlap with the periodic
148
+ /// neighbors.
149
+ #[default]
150
+ Cut,
151
+ /// If necessary, grow the box of the output structure to fit a whole number of template boxes.
152
+ Grow,
153
+ }
154
+
155
+ #[derive(Debug, Default, Clone, ValueEnum, PartialEq, Eq)]
156
+ pub enum PeriodicMode {
157
+ /// Include the periodic images of the structure when checking whether a solvent spot is
158
+ /// occupied.
159
+ #[default]
160
+ Periodic,
161
+ /// Ignore the periodic images of the structure for the solvent placement check.
162
+ Ignore,
163
+ /// Treat any structure atoms outside of the output box as an error and exit.
164
+ Deny,
165
+ }
166
+
167
+ #[derive(Debug, Clone)]
168
+ pub struct SubstituteConfig {
169
+ name: String,
170
+ quantity: Quantity,
171
+ }
172
+
173
+ impl SubstituteConfig {
174
+ /// Bake into a [`Substitute`] according to a final volume in nm³ and some number of valid
175
+ /// solvent beads.
176
+ pub fn bake(self, volume: f64, solvent_beads: u64) -> Substitute {
177
+ Substitute {
178
+ name: self.name,
179
+ number: self.quantity.number(volume, solvent_beads),
180
+ }
181
+ }
182
+ }
183
+
184
+ impl FromStr for SubstituteConfig {
185
+ type Err = String;
186
+
187
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
188
+ let mut words = s.split(':');
189
+ let name = words
190
+ .next()
191
+ .ok_or("expected a name, then a colon, followed by a quantity".to_string())?
192
+ .to_string();
193
+ let quantity = words
194
+ .next()
195
+ .ok_or("expected a quantifier after the colon".to_string())?
196
+ .parse()?;
197
+ Ok(Self { name, quantity })
198
+ }
199
+ }
200
+
201
+ #[derive(Debug, Clone)]
202
+ pub struct ChargeConfig {
203
+ charge: i64,
204
+ positive: String,
205
+ negative: String,
206
+ }
207
+
208
+ impl FromStr for ChargeConfig {
209
+ type Err = String;
210
+
211
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
212
+ let mut words = s.split(':');
213
+ let charge = words
214
+ .next()
215
+ .ok_or("expected a charge")?
216
+ .parse::<i64>()
217
+ .map_err(|err| err.to_string())?;
218
+ let (positive, negative) = if let Some(names) = words.next() {
219
+ names
220
+ .split_once(',')
221
+ .ok_or("expected two substitute names separated by a comma")?
222
+ } else {
223
+ ("NA", "CL")
224
+ };
225
+ Ok(Self {
226
+ charge,
227
+ positive: positive.to_string(),
228
+ negative: negative.to_string(),
229
+ })
230
+ }
231
+ }
232
+
233
+ impl ChargeConfig {
234
+ /// Bake the [`ChargeConfig`] into a [`Substitute`] if applicable.
235
+ ///
236
+ /// If the charge is 0, no `Substitute` is returned since there is no charge to neutralize.
237
+ pub fn bake(self) -> Option<Substitute> {
238
+ // Choose the name of the ion to neutralize with. If the charge to compensate is negative,
239
+ // we compensate with the positive ion substitute, and vice versa.
240
+ let name = match self.charge {
241
+ ..0 => self.positive,
242
+ 0 => return None,
243
+ 1.. => self.negative,
244
+ };
245
+
246
+ Some(Substitute {
247
+ name,
248
+ number: self.charge.unsigned_abs(),
249
+ })
250
+ }
251
+ }
252
+
253
+ #[derive(Debug, Clone)]
254
+ enum Quantity {
255
+ /// A fixed number.
256
+ Number(u64),
257
+ /// A fraction of the valid solvent beads.
258
+ Ratio(f64),
259
+ /// Molarity in mol/L.
260
+ Molarity(f64),
261
+ }
262
+
263
+ impl FromStr for Quantity {
264
+ type Err = String;
265
+
266
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
267
+ if let Some(molarity) = s.strip_suffix('M') {
268
+ // Molarity.
269
+ return Ok(Self::Molarity(
270
+ molarity.parse::<f64>().map_err(|err| err.to_string())?,
271
+ ));
272
+ }
273
+
274
+ if let Ok(number) = s.parse::<u64>() {
275
+ return Ok(Self::Number(number));
276
+ }
277
+
278
+ if let Ok(ratio) = s.parse::<f64>() {
279
+ if !(0.0..=1.0).contains(&ratio) {
280
+ return Err(format!(
281
+ "ratio value must satisfy `1.0 >= r >= 0.0`, found {ratio}"
282
+ ));
283
+ }
284
+ return Ok(Self::Ratio(ratio));
285
+ }
286
+
287
+ Err(format!("could not parse quantity {s:?}"))
288
+ }
289
+ }
290
+
291
+ impl Quantity {
292
+ /// Given some volume in nm³ and some number of initial valid solvent beads, determine the
293
+ /// number of beads according to this [`Quantity`].
294
+ fn number(self, volume: f64, solvent_beads: u64) -> u64 {
295
+ const N_AVOGADRO: f64 = 6.0221415e23; // per mol.
296
+ let volume_liter = volume * 1e-24;
297
+ let number = match self {
298
+ Quantity::Number(number) => number,
299
+ Quantity::Ratio(ratio) => (solvent_beads as f64 * ratio).round() as u64,
300
+ Quantity::Molarity(molarity) => (volume_liter * molarity * N_AVOGADRO).round() as u64,
301
+ };
302
+ assert!(
303
+ number <= solvent_beads,
304
+ "the number of substitutions that are specified ({number}) exceeds the number of \
305
+ solvent positions that can be substituted ({solvent_beads})"
306
+ );
307
+ number
308
+ }
309
+ }
310
+
311
+ pub struct Substitute {
312
+ pub name: String,
313
+ pub number: u64,
314
+ }
315
+
316
+ #[derive(ValueEnum, Default, Clone, Debug)]
317
+ pub enum SortBehavior {
318
+ #[default]
319
+ Size,
320
+ RevSize,
321
+ Alphabetical,
322
+ RevAlphabetical,
323
+ No,
324
+ }
solvate/convert.rs ADDED
@@ -0,0 +1,25 @@
1
+ /// Convert between `Vec3` types.
2
+ ///
3
+ /// In this program we use both [`glam::Vec3`] and the [`eightyseven::structure::Vec3`] type, which
4
+ /// is an export of some version of `glam::Vec3` used in `eightyseven`.
5
+ ///
6
+ /// This is very annoying, because we can't just use them interchangeably despite the types being
7
+ /// otherwise identical.
8
+ pub trait Convert<V> {
9
+ /// Convert between `Vec3` types.
10
+ fn convert(&self) -> V;
11
+ }
12
+
13
+ impl Convert<glam::Vec3> for eightyseven::structure::Vec3 {
14
+ #[inline(always)]
15
+ fn convert(&self) -> glam::Vec3 {
16
+ glam::Vec3::from_array(self.to_array())
17
+ }
18
+ }
19
+
20
+ impl Convert<eightyseven::structure::Vec3> for glam::Vec3 {
21
+ #[inline(always)]
22
+ fn convert(&self) -> eightyseven::structure::Vec3 {
23
+ eightyseven::structure::Vec3::from_array(self.to_array())
24
+ }
25
+ }
solvate/cookies.rs ADDED
@@ -0,0 +1,185 @@
1
+ use eightyseven::structure::ResName;
2
+ use glam::{BVec3, IVec3, UVec3, Vec3, Vec3A};
3
+
4
+ use crate::PeriodicMode;
5
+ use crate::convert::Convert;
6
+ use crate::placement::{index_3d, iter_3d};
7
+ use crate::structure::{BoxVecsExtension, Structure};
8
+
9
+ /// A simple spatial data structure that is used to accelerate solvent-structure collision checks.
10
+ ///
11
+ /// Each _cookie_ represents one solvent box, and is constructed in such a manner that all
12
+ /// positions that are in or sufficiently close to the solvent beads represented by a cookie are
13
+ /// stored in the cookie.
14
+ pub struct Cookies {
15
+ dimensions: UVec3,
16
+ cookie_size: Vec3,
17
+ cookies: Box<[Box<[Vec3]>]>,
18
+ }
19
+
20
+ impl Cookies {
21
+ /// Creates a new [`Cookies`].
22
+ ///
23
+ /// Note that the box size for the `structure` are treated as the dimensions when considering
24
+ /// the treatment of atoms according to the [`PeriodicMode`].
25
+ pub fn new(
26
+ structure: &Structure,
27
+ ignore: &[ResName],
28
+ cookie_size: Vec3,
29
+ dimensions: UVec3,
30
+ cutoff: f32,
31
+ mode: PeriodicMode,
32
+ ) -> Self {
33
+ let n_cells = dimensions.x as usize * dimensions.y as usize * dimensions.z as usize;
34
+ let mut cookies = vec![Vec::new(); n_cells];
35
+
36
+ // Place the bead's position in the appropriate cookie.
37
+ // Like raisins or pieces of chocolate. But in our case the cookies aren't round but
38
+ // cuboid. Look, I don't even remember why I picked this name.
39
+ let box_dimensions = structure.boxvecs.as_vec3();
40
+ for (i, bead) in structure.atoms.iter().enumerate() {
41
+ let mut pos = bead.position.convert();
42
+ if ignore.contains(&bead.resname) {
43
+ // We don't even consider this particle in setting up our Cookies because we want
44
+ // to ignore it in our solvent-water check.
45
+ continue;
46
+ }
47
+ match mode {
48
+ PeriodicMode::Periodic => pos = pos.rem_euclid(box_dimensions),
49
+ PeriodicMode::Ignore => {
50
+ if pos.cmplt(Vec3::ZERO).any() || pos.cmpgt(box_dimensions).any() {
51
+ continue;
52
+ }
53
+ }
54
+ PeriodicMode::Deny => {
55
+ if pos.cmplt(Vec3::ZERO).any() || pos.cmpgt(box_dimensions).any() {
56
+ eprintln!(
57
+ "ERROR: Atom {i} with position {pos} lies outside the box {box_dimensions}."
58
+ );
59
+ // FIXME: It's rather gross to me to exit from inside this function. A nice
60
+ // improvement could be to bubble it up as an Err to the caller.
61
+ std::process::exit(2);
62
+ }
63
+ }
64
+ }
65
+ // Note that rem_euclid or our PeriodicMode::Deny check may return negative zeros,
66
+ // which may subtly break our assumptions for determining cookie membership.
67
+ // Perhaps overly strict but better be safe.
68
+ let pos = pos.abs();
69
+ let cell_pos = (pos / cookie_size).floor().as_uvec3();
70
+ let idx = index_3d(cell_pos, dimensions);
71
+ cookies[idx].push(pos);
72
+ }
73
+
74
+ // Now, we have to convolve the contents of the cookies to their neighbors. Otherwise, a
75
+ // neighboring bead may be too close to some solvent bead but go unnoticed in the collision
76
+ // check.
77
+ let idimensions = dimensions.as_ivec3();
78
+ let da = Vec3A::from(box_dimensions);
79
+ // NOTE: If we are ever _really_ pressed for memory, we can win quite a bit here. Cloning
80
+ // the cookies here saves some time gluing the neighbors onto the original positions. But,
81
+ // there are ways of removing this clone.
82
+ let mut convolved_cookies = cookies.clone();
83
+ for cell_pos in iter_3d(dimensions) {
84
+ // This closure returns true if the provided position is in cutoff-range of the present
85
+ // cookie.
86
+ let is_in_range = {
87
+ let start = Vec3A::from(cookie_size * cell_pos.as_vec3() - cutoff);
88
+ let end = Vec3A::from(cookie_size * (cell_pos + 1).as_vec3() + cutoff);
89
+ move |v: &Vec3A| v.cmpge(start).all() && v.cmple(end).all()
90
+ };
91
+ let idx = index_3d(cell_pos, dimensions);
92
+
93
+ for offset in NEIGHBORS {
94
+ let neighbor_pos = cell_pos.as_ivec3() + offset;
95
+ // Note that these jumps are mutually exclusive.
96
+ let backward_jumps = neighbor_pos.cmplt(IVec3::ZERO);
97
+ let forward_jumps = neighbor_pos.cmpge(idimensions);
98
+ // Sanity check of their mutual exclusivity.
99
+ assert_eq!(backward_jumps & forward_jumps, BVec3::FALSE);
100
+
101
+ if !(backward_jumps | forward_jumps).any() {
102
+ // Default case, no complicated stuff with periodicity to consider.
103
+ // TODO: Take only the positions that are closer than `cutoff` to the
104
+ // central cell.
105
+ let neighbor_pos = neighbor_pos.as_uvec3();
106
+ let neighbor_idx = index_3d(neighbor_pos, dimensions);
107
+ let neighbor_content = cookies[neighbor_idx]
108
+ .iter()
109
+ .map(|&v| Vec3A::from(v))
110
+ .filter(is_in_range)
111
+ .map(Vec3::from);
112
+ convolved_cookies[idx].extend(neighbor_content);
113
+ } else {
114
+ fn apply(b: BVec3, v: Vec3A) -> Vec3A {
115
+ UVec3::new(b.x as u32, b.y as u32, b.z as u32).as_vec3a() * v
116
+ }
117
+
118
+ let wrapped_neighbor_pos = neighbor_pos.rem_euclid(idimensions).as_uvec3();
119
+ let wrapped_neighbor_idx = index_3d(wrapped_neighbor_pos, dimensions);
120
+ let wrapped_neighbor_content = cookies[wrapped_neighbor_idx]
121
+ .iter()
122
+ .map(|&v| {
123
+ let translation = apply(backward_jumps, -da) + apply(forward_jumps, da);
124
+ Vec3A::from(v) + translation
125
+ })
126
+ .filter(is_in_range)
127
+ .map(Vec3::from);
128
+ convolved_cookies[idx].extend(wrapped_neighbor_content);
129
+ }
130
+ }
131
+ }
132
+
133
+ Self {
134
+ dimensions,
135
+ cookie_size,
136
+ cookies: convolved_cookies
137
+ .into_iter()
138
+ .map(|cookie| cookie.into_boxed_slice())
139
+ .collect(),
140
+ }
141
+ }
142
+
143
+ pub fn get(&self, cell_pos: UVec3) -> Option<&[Vec3]> {
144
+ let idx = index_3d(cell_pos, self.dimensions);
145
+ self.cookies.get(idx).map(|cookie| &cookie[..])
146
+ }
147
+
148
+ /// Calculate the offset of a cookie at some position from the system origin.
149
+ pub fn offset(&self, cell_pos: UVec3) -> Vec3 {
150
+ cell_pos.as_vec3() * self.cookie_size()
151
+ }
152
+
153
+ pub fn cookie_size(&self) -> Vec3 {
154
+ self.cookie_size
155
+ }
156
+ }
157
+
158
+ const NEIGHBORS: [IVec3; 26] = [
159
+ IVec3::new(-1, -1, -1),
160
+ IVec3::new(0, -1, -1),
161
+ IVec3::new(1, -1, -1),
162
+ IVec3::new(-1, 0, -1),
163
+ IVec3::new(0, 0, -1),
164
+ IVec3::new(1, 0, -1),
165
+ IVec3::new(-1, 1, -1),
166
+ IVec3::new(0, 1, -1),
167
+ IVec3::new(1, 1, -1),
168
+ IVec3::new(-1, -1, 0),
169
+ IVec3::new(0, -1, 0),
170
+ IVec3::new(1, -1, 0),
171
+ IVec3::new(-1, 0, 0),
172
+ IVec3::new(1, 0, 0),
173
+ IVec3::new(-1, 1, 0),
174
+ IVec3::new(0, 1, 0),
175
+ IVec3::new(1, 1, 0),
176
+ IVec3::new(-1, -1, 1),
177
+ IVec3::new(0, -1, 1),
178
+ IVec3::new(1, -1, 1),
179
+ IVec3::new(-1, 0, 1),
180
+ IVec3::new(0, 0, 1),
181
+ IVec3::new(1, 0, 1),
182
+ IVec3::new(-1, 1, 1),
183
+ IVec3::new(0, 1, 1),
184
+ IVec3::new(1, 1, 1),
185
+ ];