FEAT 3
Finite Element Analysis Toolbox
Loading...
Searching...
No Matches
voxel_map.cpp
1// FEAT3: Finite Element Analysis Toolbox, Version 3
2// Copyright (C) 2010 by Stefan Turek & the FEAT group
3// FEAT3 is released under the GNU General Public License version 3,
4// see the file 'copyright.txt' in the top level directory for details.
5
6// includes, FEAT
7#include <kernel/geometry/voxel_map.hpp>
8
9// includes, thirdparty
10#ifdef FEAT_HAVE_ZLIB
11#include <zlib.h>
12#endif // FEAT_HAVE_ZLIB
13
14namespace FEAT
15{
16 namespace Geometry
17 {
19 typedef std::int64_t i64;
21 typedef std::uint64_t u64;
22
24 _create_stage(0),
25 _bbox_min(),
26 _bbox_max(),
27 _num_points(),
28 _stride_line(0u),
29 _stride_plane(0u),
30 _num_lines(0u),
31 _num_planes(0u),
32 _voxel_map(),
33 _out_of_bounds_value(true),
34 _coverage(0u)
35 {
36 }
37
39 _create_stage(other._create_stage),
40 _bbox_min(other._bbox_min),
41 _bbox_max(other._bbox_max),
42 _num_points(other._num_points),
43 _stride_line(other._stride_line),
44 _stride_plane(other._stride_plane),
45 _num_lines(other._num_lines),
46 _num_planes(other._num_planes),
47 _voxel_map(std::forward<std::vector<char>>(other._voxel_map)),
48 _out_of_bounds_value(other._out_of_bounds_value),
49 _coverage(other._coverage)
50 {
51 }
52
54 VoxelMap& VoxelMap::operator=(VoxelMap&& other)
55 {
56 if(this == &other)
57 return *this;
58
59 _create_stage = other._create_stage;
60 _bbox_min = other._bbox_min;
61 _bbox_max = other._bbox_max;
62 _num_points = other._num_points;
63 _stride_line = other._stride_line;
64 _stride_plane = other._stride_plane;
65 _num_lines = other._num_lines;
66 _num_planes = other._num_planes;
67 _voxel_map = std::forward<std::vector<char>>(other._voxel_map);
68 _out_of_bounds_value = other._out_of_bounds_value;
69 _coverage = other._coverage;
70
71 return *this;
72 }
73
76 {
77 }
78
79 void VoxelMap::set_bounding_box_2d(Real x_min, Real x_max, Real y_min, Real y_max)
80 {
81 XASSERTM(x_min < x_max, "invalid X dimensions for voxel map bounding box");
82 XASSERTM(y_min < y_max, "invalid Y dimensions for voxel map bounding box");
83 _bbox_min[0u] = i64(x_min * Real(unit_size));
84 _bbox_max[0u] = i64(x_max * Real(unit_size));
85 _bbox_min[1u] = i64(y_min * Real(unit_size));
86 _bbox_max[1u] = i64(y_max * Real(unit_size));
87 _bbox_min[2u] = _bbox_max[2u] = i64(0);
88 XASSERTM(_bbox_min[0u] + 1 < _bbox_max[0u], "bounding box is too small in X dimension");
89 XASSERTM(_bbox_min[1u] + 1 < _bbox_max[1u], "bounding box is too small in Y dimension");
90 }
91
92 void VoxelMap::set_bounding_box_3d(Real x_min, Real x_max, Real y_min, Real y_max, Real z_min, Real z_max)
93 {
94 XASSERTM(x_min < x_max, "invalid X dimensions for voxel map bounding box");
95 XASSERTM(y_min < y_max, "invalid Y dimensions for voxel map bounding box");
96 XASSERTM(z_min < z_max, "invalid Z dimensions for voxel map bounding box");
97 _bbox_min[0u] = i64(x_min * Real(unit_size));
98 _bbox_max[0u] = i64(x_max * Real(unit_size));
99 _bbox_min[1u] = i64(y_min * Real(unit_size));
100 _bbox_max[1u] = i64(y_max * Real(unit_size));
101 _bbox_min[2u] = i64(z_min * Real(unit_size));
102 _bbox_max[2u] = i64(z_max * Real(unit_size));
103 XASSERTM(_bbox_min[0u] + 1 < _bbox_max[0u], "bounding box is too small in X dimension");
104 XASSERTM(_bbox_min[1u] + 1 < _bbox_max[1u], "bounding box is too small in Y dimension");
105 XASSERTM(_bbox_min[2u] + 1 < _bbox_max[2u], "bounding box is too small in Z dimension");
106 }
107
108 void VoxelMap::set_num_points(Index num_x, Index num_y, Index num_z)
109 {
110 XASSERT(num_x > 1ull);
111 XASSERT(num_y > 1ull);
112 XASSERT((num_z > 1ull) || (_bbox_min[2u] == _bbox_max[2u]));
113 _num_points[0] = num_x;
114 _num_points[1] = num_y;
115 _num_points[2] = (num_z > 0u ? num_z : u64(1));
116
117 // compute other quantities
122
123 // allocate voxel map
125 }
126
128 {
129 XASSERTM(max_res > 1E-12, "invalid resolution for voxel map");
131 u64(_bbox_max[0u] - _bbox_min[0u]) / u64(max_res * Real(unit_size)) + 1u,
132 u64(_bbox_max[1u] - _bbox_min[1u]) / u64(max_res * Real(unit_size)) + 1u,
133 u64(_bbox_max[2u] - _bbox_min[2u]) / u64(max_res * Real(unit_size)) + 1u);
134 }
135
136 void VoxelMap::compute_map_from_formula_2d(const Dist::Comm& comm, const String& formula, bool gather_to_all)
137 {
138#ifdef FEAT_HAVE_FPARSER
139 VoxelFormulaMasker<2> masker(formula);
140 this->_compute_voxel_map(comm, masker, gather_to_all);
141#else
142 XABORTM("FEAT is not build and lined against FPARSER third-party library!");
143 (void)comm;
144 (void)formula;
145 (void)gather_to_all;
146#endif
147 }
148
149 void VoxelMap::compute_map_from_formula_3d(const Dist::Comm& comm, const String& formula, bool gather_to_all)
150 {
151#ifdef FEAT_HAVE_FPARSER
152 VoxelFormulaMasker<3> masker(formula);
153 this->_compute_voxel_map(comm, masker, gather_to_all);
154#else
155 XABORTM("FEAT is not build and lined against FPARSER third-party library!");
156 (void)comm;
157 (void)formula;
158 (void)gather_to_all;
159#endif
160 }
161
162 void VoxelMap::compute_map_from_off_3d(const Dist::Comm& comm, const String& filename, bool invert, bool gather_to_all)
163 {
164 XASSERTM(_bbox_min[2u] < _bbox_max[2u], "CGAL OFF voxel map creation is only available in 3D");
165 std::stringstream sstr;
166 DistFileIO::read_common(sstr, filename, comm);
167 this->compute_map_from_off_3d(comm, sstr, invert, gather_to_all);
168 }
169
170 void VoxelMap::compute_map_from_off_3d(const Dist::Comm& comm, std::istream& is, bool invert, bool gather_to_all)
171 {
172 XASSERTM(_bbox_min[2u] < _bbox_max[2u], "CGAL OFF voxel map creation is only available in 3D");
173#ifdef FEAT_HAVE_CGAL
175 VoxelCGALMasker<double> cgal_masker(cgal_wrapper, invert);
176 this->_compute_voxel_map(comm, cgal_masker, gather_to_all);
177#else
178 XABORTM("FEAT is not build and linked against CGAL third-party library!");
179 (void)comm;
180 (void)is;
181 (void)invert;
182 (void)gather_to_all;
183#endif
184 }
185
186 VoxelMap::WriteResult VoxelMap::write(const String& filename, const u64 compress_block_size, const int compress_level) const
187 {
188 std::ofstream ofs(filename, std::ios_base::out | std::ios_base::binary);
189 if(!ofs.is_open() || !ofs.good())
190 throw FileNotCreated(filename);
191
192 return write(ofs, compress_block_size, compress_level);
193 }
194
195 VoxelMap::WriteResult VoxelMap::write(std::ostream& os, const u64 compress_block_size, const int compress_level) const
196 {
197 std::size_t written = 0u;
198
199 // set up file header
200 FileHeader header;
201 memset(&header, 0, sizeof(header));
202 header.magic_number = magic_number;
203 header.header_size = sizeof(header);
204 header.min_x = _bbox_min[0];
205 header.max_x = _bbox_max[0];
206 header.num_x = _num_points[0];
207 header.stride_line = _stride_line;
208 header.min_y = _bbox_min[1];
209 header.max_y = _bbox_max[1];
210 header.num_y = _num_points[1];
211 header.stride_plane = _stride_plane;
212 header.min_z = _bbox_min[2];
213 header.max_z = _bbox_max[2];
214 header.num_z = _num_points[2];
215 header.stride_volume = _stride_plane * _num_points[2];
216 header.coverage = _coverage;
217
218 // _voxel_map may contain trailing padding bytes
219 XASSERT(header.stride_volume <= _voxel_map.size());
220
221 // do we have to compress the map?
222#ifdef FEAT_HAVE_ZLIB
223 if(compress_block_size > 0u)
224 {
225 XASSERTM(compress_block_size < 1024u, "maximum compression block size is 1024 MB");
226
227 // compute number of planes per compression block
228 header.planes_per_block = (compress_block_size*1024ull*1024ull) / _stride_plane;
229 XASSERTM(header.planes_per_block > 0ull, "compression block size is to small to hold a single plane!");
230
231 header.num_blocks = (_num_planes + header.planes_per_block - 1u) / header.planes_per_block;
232 XASSERTM(header.num_blocks > 0ull, "invalid number of compression blocks!");
233 }
234#endif // FEAT_HAVE_ZLIB
235
236 // write header
237 os.write(reinterpret_cast<char*>(&header), sizeof(header));
238 written += sizeof(header);
239
240 // write uncompressed voxel map data
241 if(header.num_blocks == 0ull)
242 {
243 // write uncompressed voxel map data
244 os.write(reinterpret_cast<const char*>(_voxel_map.data()), std::streamsize(header.stride_volume));
245 return WriteResult(written + header.stride_volume);
246 }
247
248#ifdef FEAT_HAVE_ZLIB
249 // ensure that a compression block is not larger than 1 GB
250 XASSERTM(header.planes_per_block * _stride_plane < 1073741824ull, "voxel map plane compression block size is too big");
251
252 // compression buffers
253 std::vector<std::vector<char>> compress_buffers(header.num_blocks);
254
255 // compression block entries
256 std::vector<u64> compress_blocks(header.num_blocks, 0u);
257
258 // get compression level
259 const int cmp_lvl = Math::max(Math::min(compress_level, 9), 0);
260
261 // accumulated ZLIB results
262 int failures = 0;
263
264 // loop over all compression blocks
265 FEAT_PRAGMA_OMP(parallel for schedule(dynamic,1) reduction(+:failures))
266 for(u64 iblock = 0; iblock < header.num_blocks; ++iblock)
267 {
268 // compute first and last planes
269 u64 first_plane = iblock * header.planes_per_block;
270 u64 block_bytes = Math::min(header.planes_per_block, _num_planes - first_plane) * _stride_plane;
271
272 // estimate compression buffer size
273 u64 buffer_size = u64(::compressBound(uLong(block_bytes)));
274
275 // allocate compression buffer
276 compress_buffers[iblock].resize(buffer_size);
277
278 // compression buffer size
279 uLongf dest_size = uLongf(buffer_size);
280
281 // compress via ZLIB
282 int rtn = ::compress2(reinterpret_cast<Bytef*>(compress_buffers[iblock].data()), &dest_size,
283 reinterpret_cast<const Bytef*>(&_voxel_map[first_plane * _stride_plane]), uLongf(block_bytes), cmp_lvl);
284
285 // failure?
286 if(rtn != Z_OK)
287 {
288 ++failures;
289 continue;
290 }
291
292 // save compression buffer size
293 compress_blocks[iblock] = u64(dest_size);
294 }
295
296 // did any compression fail?
297 XASSERTM(failures == 0, "ZLIB failed to compress at least one plane block!");
298
299 // write the compression block headers
300 os.write(reinterpret_cast<const char*>(compress_blocks.data()), std::streamsize(compress_blocks.size() * sizeof(u64)));
301 written += compress_blocks.size() * sizeof(u64);
302
303 // okay, loop over all compression blocks and write them out
304 u64 compress_size = 0u;
305 for(std::size_t i(0); i < compress_buffers.size(); ++i)
306 {
307 os.write(reinterpret_cast<const char*>(compress_buffers[i].data()), std::streamsize(compress_blocks[i]));
308 written += compress_blocks[i];
309 compress_size += compress_blocks[i];
310 }
311
312 // return result
313 return WriteResult(written, compress_block_size, u64(compress_level), compress_buffers.size(), compress_size);
314#else
315 // we should never arrive in this #else case even if compiling without zlib
316 (void)compress_block_size;
317 (void)compress_level;
318 XABORTM("INTERNAL ERROR");
319#endif // FEAT_HAVE_ZLIB
320 }
321
323 {
324 BinaryStream stream;
325 DistFileIO::read_common(stream, filename, comm);
326 return read(stream, filename);
327 }
328
329 VoxelMap::ReadResult VoxelMap::read(std::istream& is, const String& filename)
330 {
331 // set up file header
332 FileHeader header;
333 memset(&header, 0, sizeof(header));
334
335 // try to read header
336 if(!is.read(reinterpret_cast<char*>(&header), sizeof(header)).good())
337 throw VoxelMapFileError(filename, "Failed to read voxel map header");
338
339 u64 bytes_read = sizeof(header);
340
341 // check magic number
342 if(header.magic_number != magic_number)
343 throw VoxelMapFileError(filename, "File does not seems to be a voxel map file");
344
345 // check header size
346 if(header.header_size != header_size)
347 throw VoxelMapFileError(filename, String("invalid header size; expected ") + stringify(header_size) + " but got " + stringify(header.header_size));
348
349 // perform some sanity checks
350 if((header.num_x > 0u) && (header.max_x <= header.min_x))
351 throw VoxelMapFileError(filename, String("invalid X dimensions: x_max <= x_min"));
352 if((header.num_y > 0u) && (header.max_y <= header.min_y))
353 throw VoxelMapFileError(filename, String("invalid Y dimensions: y_max <= y_min"));
354 if((header.num_z > 0u) && (header.max_z <= header.min_z))
355 throw VoxelMapFileError(filename, String("invalid Z dimensions: z_max <= z_min"));
356 if((header.num_x == 0u) && (header.max_x != header.min_x))
357 throw VoxelMapFileError(filename, String("invalid X dimensions: x_max != x_min for num_x = 0"));
358 if((header.num_y == 0u) && (header.max_y != header.min_y))
359 throw VoxelMapFileError(filename, String("invalid Y dimensions: y_max != y_min for num_y = 0"));
360 if((header.num_z == 0u) && (header.max_z != header.min_z))
361 throw VoxelMapFileError(filename, String("invalid Z dimensions: z_max != z_min for num_z = 0"));
362 if(header.stride_line*8u < header.num_x)
363 throw VoxelMapFileError(filename, String("invalid line stride: too small for a single X-line"));
364 if((header.stride_line & 0xF) != 0)
365 throw VoxelMapFileError(filename, String("invalid line stride: not a multiple of 16"));
366 if(header.stride_plane < header.num_y * header.stride_line)
367 throw VoxelMapFileError(filename, String("invalid plane stride: too small for a single XY-plane"));
368 if((header.stride_plane & 0xF) != 0)
369 throw VoxelMapFileError(filename, String("invalid plane stride: not a multiple of 16"));
370 if(header.stride_volume < header.num_z * header.stride_plane)
371 throw VoxelMapFileError(filename, String("invalid volume stride: too small for a single XYZ-volume"));
372 if((header.stride_volume & 0xF) != 0)
373 throw VoxelMapFileError(filename, String("invalid plane stride: not a multiple of 16"));
374
375 // ok, extract the vital data
376 _bbox_min[0u] = header.min_x;
377 _bbox_max[0u] = header.max_x;
378 _bbox_min[1u] = header.min_y;
379 _bbox_max[1u] = header.max_y;
380 _bbox_min[2u] = header.min_z;
381 _bbox_max[2u] = header.max_z;
382 _num_points[0u] = header.num_x;
383 _num_points[1u] = header.num_y;
384 _num_points[2u] = header.num_z;
385 _stride_line = header.stride_line;
386 _stride_plane = header.stride_plane;
389 _coverage = header.coverage;
390
391 // allocate voxel map
392 _voxel_map.resize(header.stride_volume);
393
394 // don't we have compression blocks?
395 if(header.num_blocks == 0u)
396 {
397 // just read the raw map data
398 if(!is.read(_voxel_map.data(), std::streamsize(header.stride_volume)).good())
399 throw VoxelMapFileError(filename, "Failed to read uncompressed voxel map buffer");
400 bytes_read += u64(is.gcount());
401 return ReadResult(bytes_read);
402 }
403
404 // we're dealing with a ZLIB compressed voxel map
405
406#ifdef FEAT_HAVE_ZLIB
407 // read compression blocks
408 std::vector<u64> blocks(header.num_blocks);
409 if(!is.read(reinterpret_cast<char*>(blocks.data()), std::streamsize(header.num_blocks * sizeof(u64))).good())
410 throw VoxelMapFileError(filename, "Failed to read voxel map compression blocks");
411 bytes_read += u64(is.gcount());
412
413 // compute buffer offsets and total buffer size
414 std::vector<std::size_t> buffer_offset(header.num_blocks + 1u);
415 buffer_offset[0u] = 0u;
416 for(std::size_t i(0); i < blocks.size(); ++i)
417 buffer_offset[i+1u] = buffer_offset[i] + blocks[i];
418
419 // read compression buffer
420 std::vector<char> compression_buffer(buffer_offset.back());
421 if(!is.read(compression_buffer.data(), std::streamsize(compression_buffer.size())).good())
422 throw VoxelMapFileError(filename, "Failed to read compressed voxel map buffer");
423 bytes_read += u64(is.gcount());
424
425 // accumulated ZLIB results
426 int failures = 0;
427
428 // loop over all compression blocks
429 FEAT_PRAGMA_OMP(parallel for schedule(dynamic,1) reduction(+:failures))
430 for(u64 iblock = 0; iblock < header.num_blocks; ++iblock)
431 {
432 u64 first_plane = iblock * header.planes_per_block;
433 u64 block_bytes = Math::min(header.planes_per_block, _num_planes - first_plane) * _stride_plane;
434 u64 compress_size = blocks[iblock];
435
436 // decompress
437 uLongf dest_size = uLongf(block_bytes);
438 int rtn = ::uncompress(reinterpret_cast<Bytef*>(&_voxel_map[first_plane * _stride_plane]), &dest_size,
439 reinterpret_cast<const Bytef*>(&compression_buffer[buffer_offset[iblock]]), uLong(compress_size));
440
441 if(rtn != Z_OK)
442 {
443 ++failures;
444 continue;
445 }
446 }
447
448 // did any compression fail?
449 XASSERTM(failures == 0, "ZLIB failed to decompress at least one plane block!");
450 return ReadResult(bytes_read, header.num_blocks, compression_buffer.size());
451#else
452 XABORTM("Cannot read compressed voxel map file, because FEAT was compiled without the ZLIB third-party library");
453#endif // FEAT_HAVE_ZLIB
454 }
455
456 void VoxelMap::export_to_bmp(const String& filename_prefix) const
457 {
458 XASSERTM(_num_points[2] <= 20000, "voxel map is too big for BMP export!");
459
460 for(u64 iplane(0); iplane < _num_planes; ++iplane)
461 {
462 String filename = filename_prefix + "." + stringify(iplane).pad_front(5, '0') + ".bmp";
463 std::ofstream ofs(filename, std::ios_base::binary);
464 _export_plane_to_bmp(ofs, iplane);
465 ofs.close();
466 }
467 }
468
469 void VoxelMap::export_plane_to_bmp(const String& filename, Real z_coord) const
470 {
471 std::ofstream ofs(filename, std::ios_base::binary);
472 export_plane_to_bmp(ofs, z_coord);
473 ofs.close();
474 }
475
476 void VoxelMap::export_plane_to_bmp(std::ostream& os, Real z_coord) const
477 {
479 }
480
481 void VoxelMap::render_to_bmp(const String& filename_prefix, Index width, Index height, Index depth) const
482 {
483 XASSERTM(depth <= 20000, "render width is too big for BMP export!");
484 for(i64 iplane(0); iplane < i64(depth); ++iplane)
485 {
486 String filename = filename_prefix + "." + stringify(iplane).pad_front(5, '0') + ".bmp";
487 std::ofstream ofs(filename, std::ios_base::binary);
488 i64 z_min = _bbox_min[2] + ( iplane * (_bbox_max[2] - _bbox_min[1])) / i64(depth);
489 i64 z_max = _bbox_min[2] + ((iplane+1) * (_bbox_max[2] - _bbox_min[1])) / i64(depth);
490 _render_plane_to_bmp(ofs, width, height, z_min, z_max);
491 ofs.close();
492 }
493 }
494
495 void VoxelMap::render_plane_to_bmp(const String& filename, Index width, Index height, Real z_min, Real z_max) const
496 {
497 std::ofstream ofs(filename, std::ios_base::binary);
498 render_plane_to_bmp(ofs, width, height, z_min, z_max);
499 ofs.close();
500 }
501
502 void VoxelMap::render_plane_to_bmp(std::ostream& os, Index width, Index height, Real z_min, Real z_max) const
503 {
504 _render_plane_to_bmp(os, width, height, i64(z_min * Real(unit_size)), i64(z_max * Real(unit_size)));
505 }
506
508 {
509 if(_num_points[2] > 0u)
510 return Real(_bbox_max[2] - _bbox_min[2]) * Real(_bbox_max[1] - _bbox_min[1]) * Real(_bbox_max[0] - _bbox_min[0]) / Math::cub(Real(unit_size));
511 if(_num_points[1] > 0u)
512 return Real(_bbox_max[1] - _bbox_min[1]) * Real(_bbox_max[0] - _bbox_min[0]) / Math::sqr(Real(unit_size));
513 if(_num_points[0] > 0u)
514 return Real(_bbox_max[0] - _bbox_min[0]) / Real(unit_size);
515 return Real(0);
516 }
517
519 {
520 if(_voxel_map.empty())
521 return u64(0);
522
523 u64 count(0u), n(_voxel_map.size()), nv(get_num_voxels());
524 FEAT_PRAGMA_OMP(parallel for reduction(+:count))
525 for(u64 i = 0u; i < n; ++i)
526 {
527 for(int k = 0; k < 8; ++k)
528 count += u64((_voxel_map[i] >> k) & 1);
529 }
530
531 // convert coverage relative to unit size
532 return (count*unit_size) / nv;
533 }
534
535 u64 VoxelMap::_map_coord_idx_nearest(i64 xyz, std::size_t sdim) const
536 {
537 // The input coordinate X is expected to be in the range of the bounding box [BMIN,BMAX], which is discretized
538 // by the NP voxel coordinates in the range {0, 1, ..., NP-1}, so applying standard linear transformation from
539 // the interval [BMIN, BMAX] to [0, NP-1] yields the formula
540 //
541 // (NP - 1) * (X - BMIN)
542 // X -> 0 + -----------------------
543 // BMAX - BMIN
544 //
545 // However, we are working with integer arithmetic here, so the fraction will be handled by integer division,
546 // which truncates all decimal places, so we have to add 1/2 to the result of the fraction to achieve a
547 // "round-to-nearest" voxel behavior. We exploit that 1/2 = (BMAX - BMIN) / (2*BMAX - 2*MIN) and so we get
548 //
549 // (NP - 1) * (X - BMIN) BMAX - BMIN
550 // X -> ----------------------- + -----------------
551 // BMAX - BMIN 2*BMAX - 2*BMIN
552 //
553 // which is equivalent to
554 //
555 // 2 * (NP - 1) * (X - BMIN) + BMAX - BMIN
556 // X -> -----------------------------------------
557 // 2 * BMAX - 2 * BMIN
558 //
559 i64 idx = (2*(i64(_num_points[sdim]) - 1) * (xyz - _bbox_min[sdim]) + _bbox_max[sdim] - _bbox_min[sdim]) / (2*_bbox_max[sdim] - 2*_bbox_min[sdim]);
560
561 // also check whether the index is out of bounds
562 return (0 <= idx) && (idx < i64(this->_num_points[sdim])) ? u64(idx) : ~u64(0);
563 }
564
565 i64 VoxelMap::_map_coord_idx_lower(i64 xyz, std::size_t sdim) const
566 {
567 // The input coordinate X is expected to be in the range of the bounding box [BMIN,BMAX], which is discretized
568 // by the NP voxel coordinates in the range {0, 1, ..., NP-1}, so applying standard linear transformation from
569 // the interval [BMIN, BMAX] to [0, NP-1] yields the formula
570 //
571 // (NP - 1) * (X - BMIN)
572 // X -> ----------------------- =: IDX
573 // BMAX - BMIN
574 //
575 // We are working with integer arithmetic here, so the fraction will be handled by integer division,
576 // which truncates all decimal places, which is what we want in this case to obtain the lower voxel index.
577 return ((i64(_num_points[sdim]) - 1) * (xyz - _bbox_min[sdim])) / (_bbox_max[sdim] - _bbox_min[sdim]);
578 }
579
580 i64 VoxelMap::_map_coord_idx_upper(i64 xyz, std::size_t sdim) const
581 {
582 // The input coordinate X is expected to be in the range of the bounding box [BMIN,BMAX], which is discretized
583 // by the NP voxel coordinates in the range {0, 1, ..., NP-1}, so applying standard linear transformation from
584 // the interval [BMIN, BMAX] to [0, NP-1] yields the formula
585 //
586 // (NP - 1) * (X - BMIN)
587 // X -> -----------------------
588 // BMAX - BMIN
589 //
590 // We are working with integer arithmetic here, so the fraction will be handled by integer division,
591 // which truncates all decimal places, so to round an integer fraction A/B up to the next integer, we have to
592 // add (B-1) to the numerator to obtain (A+B-1)/B, which in our case results in the formula
593 //
594 // (NP - 1) * (X - BMIN) + BMAX - BMIN - 1
595 // X -> ----------------------------------------- =: IDX
596 // BMAX - BMIN
597 //
598 return ((i64(_num_points[sdim]) - 1) * (xyz - _bbox_min[sdim]) + _bbox_max[sdim] - _bbox_min[sdim] - 1) / (_bbox_max[sdim] - _bbox_min[sdim]);
599 }
600
601 Real VoxelMap::_calc_sample_rate(i64 xyz, i64 idx, std::size_t sdim) const
602 {
603 // the sample rate is defined as sr(xyz,idx) := 1 - |xyz - V(idx)| / |V(idx+1) - V(idx)| where
604 //
605 // V(idx+1) - V(idx) = (V(n) - V(0)) / (n-1)
606 // and
607 // V(idx) = V(0) + idx*(V(idx+1) - V(idx)) = V(0) + idx*(V(n) - V(0)) / (n-1)
608 //
609 // so in total we have
610 //
611 // sr(xyz,idx) = 1 - |xyz - V(idx)| / |V(idx+1) - V(idx)|
612 // = 1 - |(xyz - V(idx)) / (V(idx+1) - V(idx))|
613 // = 1 - |(xyz - V(0) - idx*(V(n) - V(0)) / (n-1)) / ((V(n) - V(0)) / (n-1))|
614 // = 1 - |((xyz - V(0))*(n-1) - idx*(V(n) - V(0))) / (V(n) - V(0))|
615 // = 1 - |(xyz - V(0))*(n-1) / (V(n) - V(0)) - idx)|
616 //
617 return Real(1) - Math::abs(Real(_num_points[sdim] - 1) * Real(xyz - _bbox_min[sdim]) / Real(_bbox_max[sdim] - _bbox_min[sdim]) - Real(idx));
618 }
619
620 bool VoxelMap::_check_point(i64 xidx, i64 yidx, i64 zidx) const
621 {
622 if((xidx < i64(0)) || (i64(_num_points[0]) <= xidx))
624 if((yidx < i64(0)) || (i64(_num_points[1]) <= yidx))
626 if((zidx < i64(0)) || (i64(_num_points[2]) <= zidx))
628 return ((this->_voxel_map[zidx*this->_stride_plane + yidx*this->_stride_line + (xidx>>3)] >> (xidx & 7)) & 1u) != 0u;
629 }
630
631 bool VoxelMap::_check_point_nearest(const std::array<i64, 1>& p) const
632 {
633 u64 xidx = _map_coord_idx_nearest(p[0], 0u);
634 if(xidx == ~u64(0))
636 return (this->_voxel_map[(xidx >> 3)] >> (xidx & 0x7)) & 0x1;
637 }
638
639 bool VoxelMap::_check_point_nearest(const std::array<i64, 2>& p) const
640 {
641 u64 xidx = _map_coord_idx_nearest(p[0], 0u);
642 u64 yidx = _map_coord_idx_nearest(p[1], 1u);
643 if((xidx|yidx) == ~u64(0))
645 return (this->_voxel_map[yidx * this->_stride_line + (xidx >> 3)] >> (xidx & 0x7)) & 0x1;
646 }
647
648 bool VoxelMap::_check_point_nearest(const std::array<i64, 3>& p) const
649 {
650 u64 xidx = _map_coord_idx_nearest(p[0], 0u);
651 u64 yidx = _map_coord_idx_nearest(p[1], 1u);
652 u64 zidx = _map_coord_idx_nearest(p[2], 2u);
653 if((xidx|yidx|zidx) == ~u64(0))
655 u64 line = zidx * this->_num_points[1] + yidx;
656 return (this->_voxel_map[line * this->_stride_line + (xidx >> 3)] >> (xidx & 0x7)) & 0x1;
657 }
658
659 bool VoxelMap::_check_box(const std::array<u64, 1>& box_min, const std::array<u64, 1>& box_max) const
660 {
661 for(u64 xidx(box_min[0]); xidx <= box_max[0]; ++xidx)
662 {
663 if((this->_voxel_map[(xidx >> 3)] >> (xidx & 0x7)) & 0x1)
664 return true;
665 }
666 return false;
667 }
668
669 bool VoxelMap::_check_box(const std::array<u64, 2>& box_min, const std::array<u64, 2>& box_max) const
670 {
671 for(u64 yidx(box_min[1]); yidx <= box_max[1]; ++yidx)
672 {
673 for(u64 xidx(box_min[0]); xidx <= box_max[0]; ++xidx)
674 {
675 if((this->_voxel_map[yidx * this->_stride_line + (xidx >> 3)] >> (xidx & 0x7)) & 0x1)
676 return true;
677 }
678 }
679 return false;
680 }
681
682 bool VoxelMap::_check_box(const std::array<u64, 3>& box_min, const std::array<u64, 3>& box_max) const
683 {
684 for(u64 zidx(box_min[2]); zidx <= box_max[2]; ++zidx)
685 {
686 for(u64 yidx(box_min[1]); yidx <= box_max[1]; ++yidx)
687 {
688 u64 line = zidx * this->_num_points[1] + yidx;
689 for(u64 xidx(box_min[0]); xidx <= box_max[0]; ++xidx)
690 {
691 if((this->_voxel_map[line * this->_stride_line + (xidx >> 3)] >> (xidx & 0x7)) & 0x1)
692 return true;
693 }
694 }
695 }
696 return false;
697 }
698
699 Real VoxelMap::_sample_point_1d_x(i64 px, i64 xidx, i64 yidx, i64 zidx) const
700 {
701 Real v0 = Real(check_point(xidx, yidx, zidx));
702 Real v1 = Real(check_point(xidx+1, yidx, zidx));
703 return v0 + (v1 - v0) * _calc_sample_rate(px, xidx, 0u);
704 }
705
706 Real VoxelMap::_sample_point_1d_y(i64 py, i64 xidx, i64 yidx, i64 zidx) const
707 {
708 Real v0 = Real(check_point(xidx, yidx, zidx));
709 Real v1 = Real(check_point(xidx, yidx+1, zidx));
710 return v0 + (v1 - v0) * _calc_sample_rate(py, yidx, 1u);
711 }
712
713 Real VoxelMap::_sample_point_1d_z(i64 pz, i64 xidx, i64 yidx, i64 zidx) const
714 {
715 Real v0 = Real(check_point(xidx, yidx, zidx));
716 Real v1 = Real(check_point(xidx, yidx, zidx+1));
717 return v0 + (v1 - v0) * _calc_sample_rate(pz, zidx, 2u);
718 }
719
720 Real VoxelMap::_sample_point_2d(i64 px, i64 py, i64 xidx, i64 yidx, i64 zidx) const
721 {
722 Real v0 = _sample_point_1d_x(px, xidx, yidx, zidx);
723 Real v1 = _sample_point_1d_x(px, xidx, yidx+1, zidx);
724 return v0 + (v1 - v0) * _calc_sample_rate(py, yidx, 1u);
725 }
726
727 Real VoxelMap::_sample_point_3d(i64 px, i64 py, i64 pz, i64 xidx, i64 yidx, i64 zidx) const
728 {
729 Real v0 = _sample_point_2d(px, py, xidx, yidx, zidx);
730 Real v1 = _sample_point_2d(px, py, xidx, yidx, zidx+1);
731 return v0 + (v1 - v0) * _calc_sample_rate(pz, zidx, 2u);
732 }
733
734 Real VoxelMap::_sample_point(const std::array<i64, 1>& p) const
735 {
736 return _sample_point_1d_x(p[0], _map_coord_idx_lower(p[0], 0u));
737 }
738
739 Real VoxelMap::_sample_point(const std::array<i64, 2>& p) const
740 {
741 return _sample_point_2d(p[0], p[1], _map_coord_idx_lower(p[0], 0u), _map_coord_idx_lower(p[1], 1u));
742 }
743
744 Real VoxelMap::_sample_point(const std::array<i64, 3>& p) const
745 {
746 return _sample_point_3d(p[0], p[1], p[2], _map_coord_idx_lower(p[0], 0u),
747 _map_coord_idx_lower(p[1], 1u), _map_coord_idx_lower(p[2], 2u));
748 }
749
750 Real VoxelMap::_sample_box_1d(i64 px0, i64 px1, i64 xidx0, i64 xidx1, i64 yidx, i64 zidx) const
751 {
752 ASSERT(xidx0 < xidx1);
753
754 // is the box completely left or completely right of the voxel map domain?
755 if(xidx1 < i64(0))
757 if(i64(_num_points[0]) <= xidx0)
759
760 // sample the two voxel map entries left and right of px0
761 Real v00 = _check_point(xidx0, yidx, zidx);
762 Real v01 = _check_point(xidx0+1, yidx, zidx);
763
764 // sample the two voxel map entries left and right of px1
765 Real v10 = _check_point(xidx1-1, yidx, zidx);
766 Real v11 = _check_point(xidx1, yidx, zidx);
767
768 // compute sample rate of px0 w.r.t. left voxel V(xidx0) and of px1 w.r.t. right voxel V(xidx1)
769 Real s0 = _calc_sample_rate(px0, xidx0, 0);
770 Real s1 = _calc_sample_rate(px1, xidx1, 0);
771
772 // compute interpolated values v0 and v1 of px0 and px1, respectively, from their neighboring voxel values
773 Real v0 = v01 + (v00 - v01) * s0;
774 Real v1 = v10 + (v11 - v10) * s1;
775
776 // if the entire box [px0, px1] is between two neighboring voxels, we can integrate the interpolated voxel
777 // values v0 and v1 at px0 and px1 via the trapezoidal rule by simply computing the average of the two values:
778 if(xidx1 == xidx0 + 1)
779 return Real(0.5) * (v0 + v1);
780
781 // in any other case, we have to integrate the voxel values over the interval [px0,px1], where v0 and v1
782 // denote the previously interpolated voxel values at the interval endpoints px0 and px1, respectively:
783 //
784 // v0 v1
785 // --V------+======V=============V=====...=====V=============V======+------V
786 // xidx0 px0 xidx0+1 ... ... xidx1-1 px1 xidx1
787 // |--s0--| |--s1--|
788 //
789 // we compute the integral in three steps and then compute the sum of those three sub-integrals:
790 // 1. compute the integral over the left-most sub-interval [px0,V(xidx0+1)] via the trapezoidal rule
791 // 2. compute the integral over the right-most sub-interval [V(xidx1-1),px1] via the trapezoidal rule
792 // 3. compute the integral over the inner intervals [V(idx0+1),V(idx1-1)] via the summed trapezoidal rule
793 //
794 // for the outer two sub-interval we have that the length of...
795 // ...the left-most sub-interval [px0,V(xidx0+1)] is given by the sample rate s0 of px0 w.r.t. V(idx0)
796 // ...the right-most sub-interval [V(xidx1-1),px1] is given by the sample rate s1 of px1 w.r.t. V(idx1)
797 //
798 // therefore, the integral of
801 // ...the inner interval equals to V(idx0+1)/2 + V(idx1-1)/2 + sum_{xidx0+1<=k<=idx1-1} V(k)
802 //
803 // which results in
804 //
805 // s0*(v0+V(idx0+1))/2 + V(idx0+1)/2 + sum_{xidx0+1 < k < idx1-1} V(k) + V(idx1-1)/2 + s1*(v1+V(idx1-1))/2
806 // \___________________/ \___________________________________________________________/ \___________________/
807 // left-most interval inner intervals right-most interval
808 //
809 // which in the case where idx0+1 < idx1-1 is equal to
810 //
811 // (s0*v0)/2 + (s0+1)*V(idx0+1)/2 + sum_{xidx0+1<k<idx1-1} V(k) + (s1+1)*V(idx1-1)/2 + (s1*v1)/2
812 //
813 // there is one final special case where idx0+1 = idx1-1, i.e. there is just a single voxel between the
814 // two points px0 and px1 -- or in other words: there are no inner intervals -- and therefore we just have
815 // to add up the two integrals over the outer most sub-intervals, so for this case we get
816 //
817 // s0*(v0+V(idx0+1))/2 + s1*(v1+V(idx1-1))/2 = (s0*v0 + s1*v1 + (s0+s1)*V(idx0+1))/2
818
819 // add scaled endpoint values for trapezoidal rule of first and last intervals of length s0 and s1
820 Real result = Real(0.5) * (s0*v0 + s1*v1);
821
822 // add first and last inner voxel and scale properly to compensate for the following summed trapezoidal rule
823 if(xidx1 == xidx0 + 2)
824 {
825 // no inner X-intervals
826 result += Real(0.5) * (s0 + s1) * _check_point(xidx0+1, yidx, zidx);
827 }
828 else
829 {
830 // at least one inner X-interval exists
831 result += Real(0.5) * (s0 + Real(1)) * _check_point(xidx0+1, yidx, zidx);
832 result += Real(0.5) * (s1 + Real(1)) * _check_point(xidx1-1, yidx, zidx);
833 }
834
835 // now add all inner voxel values for summed trapezoidal rule
836 for(i64 i = xidx0+2; i < xidx1-1; ++i)
837 result += _check_point(i, yidx, zidx);
838
839 // finally divide by the total integration interval width
840 return result / (Real(xidx1 - xidx0 - 2) + s0 + s1);
841 }
842
843 Real VoxelMap::_sample_box_2d(i64 px0, i64 px1, i64 py0, i64 py1, i64 xidx0, i64 xidx1, i64 yidx0, i64 yidx1, i64 zidx) const
844 {
845 ASSERT(yidx0 < yidx1);
846
847 // is the box completely below or completely above of the voxel map domain?
848 if(yidx1 < i64(0))
850 if(i64(_num_points[1]) <= yidx0)
852
853 // sample the two X-parallel voxel map lines below and above of py0
854 Real v00 = _sample_box_1d(px0, px1, xidx0, xidx1, yidx0, zidx);
855 Real v01 = _sample_box_1d(px0, px1, xidx0, xidx1, yidx0+1, zidx);
856
857 // sample the two X-parallel voxel map lines below and above of py1
858 Real v10 = _sample_box_1d(px0, px1, xidx0, xidx1, yidx1-1, zidx);
859 Real v11 = _sample_box_1d(px0, px1, xidx0, xidx1, yidx1, zidx);
860
861 // compute sample rate of py0 w.r.t. lower voxel V(yidx0) and of py1 w.r.t. upper voxel V(yidx1)
862 Real s0 = _calc_sample_rate(py0, yidx0, 1);
863 Real s1 = _calc_sample_rate(py1, yidx1, 1);
864
865 // compute interpolated values v0 and v1 of py0 and py1, respectively, from their neighboring voxel values
866 Real v0 = v01 + (v00 - v01) * s0;
867 Real v1 = v10 + (v11 - v10) * s1;
868
869 // if the entire box [py0, py1] is between two neighboring voxel lines, we can integrate the interpolated voxel
870 // line values v0 and v1 at py0 and py1 via the trapezoidal rule by simply computing the average of the two values:
871 if(yidx1 == yidx0 + 1)
872 return Real(0.5) * (v0 + v1);
873
874 // see the comments inside _sample_box_1d() for more details about what is happening here
875
876 // add scaled endpoint values for trapezoidal rule of first and last intervals of length s0 and s1
877 Real result = Real(0.5) * (s0*v0 + s1*v1);
878
879 // add first and last inner voxel rows and scale properly to compensate for the following summed trapezoidal rule
880 if(yidx1 == yidx0 + 2)
881 {
882 // no inner Y-intervals
883 result += Real(0.5) * (s0 + s1) * _sample_box_1d(px0, px1, xidx0, xidx1, yidx0+1, zidx);
884 }
885 else
886 {
887 // at least one inner Y-interval exists
888 result += Real(0.5) * (s0 + Real(1)) * _sample_box_1d(px0, px1, xidx0, xidx1, yidx0+1, zidx);
889 result += Real(0.5) * (s1 + Real(1)) * _sample_box_1d(px0, px1, xidx0, xidx1, yidx1-1, zidx);
890 }
891
892 // now add all inner X-parallel voxel line values for summed trapezoidal rule
893 for(i64 i = yidx0+2; i < yidx1-1; ++i)
894 result += _sample_box_1d(px0, px1, xidx0, xidx1, i, zidx);
895
896 // finally divide by the total integration interval height
897 return result / (Real(yidx1 - yidx0 - 2) + s0 +s1);
898 }
899
900 Real VoxelMap::_sample_box_3d(i64 px0, i64 px1, i64 py0, i64 py1, i64 pz0, i64 pz1, i64 xidx0, i64 xidx1, i64 yidx0, i64 yidx1, i64 zidx0, i64 zidx1) const
901 {
902 ASSERT(zidx0 < zidx1);
903
904 // is the box completely below or completely above of the voxel map domain?
905 if(zidx1 < i64(0))
907 if(i64(_num_points[2]) <= zidx0)
909
910 // sample the two XY-parallel voxel map planes in front and behind of pz0
911 Real v00 = _sample_box_2d(px0, px1, py0, py1, xidx0, xidx1, yidx0, yidx1, zidx0);
912 Real v01 = _sample_box_2d(px0, px1, py0, py1, xidx0, xidx1, yidx0, yidx1, zidx0+1);
913
914 // sample the two XY-parallel voxel map planes in front and behind of pz1
915 Real v10 = _sample_box_2d(px0, px1, py0, py1, xidx0, xidx1, yidx0, yidx1, zidx1-1);
916 Real v11 = _sample_box_2d(px0, px1, py0, py1, xidx0, xidx1, yidx0, yidx1, zidx1);
917
918 // compute sample rate of pz0 w.r.t. back voxel V(zidx0) and of pz1 w.r.t. front voxel V(zidx1)
919 Real s0 = _calc_sample_rate(pz0, zidx0, 2);
920 Real s1 = _calc_sample_rate(pz1, zidx1, 2);
921
922 // compute interpolated values v0 and v1 of pz0 and pz1, respectively, from their neighboring voxel values
923 Real v0 = v01 + (v00 - v01) * s0;
924 Real v1 = v10 + (v11 - v10) * s1;
925
926 // if the entire box [pz0, pz1] is between two neighboring voxel planes, we can integrate the interpolated voxel
927 // plane values v0 and v1 at pz0 and pz1 via the trapezoidal rule by simply computing the average of the two values:
928 if(zidx1 == zidx0 + 1)
929 return Real(0.5) * (v0 + v1);
930
931 // see the comments inside _sample_box_1d() for more details about what is happening here
932
933 // add scaled endpoint values for trapezoidal rule of first and last intervals of length s0 and s1
934 Real result = Real(0.5) * (s0*v0 + s1*v1);
935
936 // add first and last inner voxel planes and scale properly to compensate for the following summed trapezoidal rule
937 if(zidx1 == zidx0 + 2)
938 {
939 // no inner Z-intervals
940 result += Real(0.5) * (s0 + s1) * _sample_box_2d(px0, px1, py0, py1, xidx0, xidx1, yidx0, yidx1, zidx0+1);
941 }
942 else
943 {
944 // at least one inner Z-interval exists
945 result += Real(0.5) * (s0 + Real(1)) * _sample_box_2d(px0, px1, py0, py1, xidx0, xidx1, yidx0, yidx1, zidx0+1);
946 result += Real(0.5) * (s1 + Real(1)) * _sample_box_2d(px0, px1, py0, py1, xidx0, xidx1, yidx0, yidx1, zidx1-1);
947 }
948
949 // now add all inner XY-parallel voxel plane values for summed trapezoidal rule
950 for(i64 i = zidx0+2; i < zidx1-1; ++i)
951 result += _sample_box_2d(px0, px1, py0, py1, xidx0, xidx1, yidx0, yidx1, i);
952
953 // finally divide by the total integration interval depth
954 return result / (Real(zidx1 - zidx0 - 2) + s0 +s1);
955 }
956
957 Real VoxelMap::_sample_box(const std::array<i64, 1>& box_min, const std::array<i64, 1>& box_max) const
958 {
959 if(_num_points[0] > 1u)
960 return _sample_box_1d(box_min[0], box_max[0],
961 _map_coord_idx_lower(box_min[0], 0u), _map_coord_idx_upper(box_max[0], 0u));
962 else
963 return Real(0);
964 }
965
966 Real VoxelMap::_sample_box(const std::array<i64, 2>& box_min, const std::array<i64, 2>& box_max) const
967 {
968 if(_num_points[1] > 1u)
969 return _sample_box_2d(box_min[0], box_max[0], box_min[1], box_max[1],
970 _map_coord_idx_lower(box_min[0], 0u), _map_coord_idx_upper(box_max[0], 0u),
971 _map_coord_idx_lower(box_min[1], 1u), _map_coord_idx_upper(box_max[1], 1u));
972 else if(_num_points[0] > 1u)
973 return _sample_box_1d(box_min[0], box_max[0],
974 _map_coord_idx_lower(box_min[0], 0u), _map_coord_idx_upper(box_max[0], 0u));
975 else
976 return Real(0);
977 }
978
979 Real VoxelMap::_sample_box(const std::array<i64, 3>& box_min, const std::array<i64, 3>& box_max) const
980 {
981 if(_num_points[2] > 1u)
982 return _sample_box_3d(box_min[0], box_max[0], box_min[1], box_max[1], box_min[2], box_max[2],
983 _map_coord_idx_lower(box_min[0], 0u), _map_coord_idx_upper(box_max[0], 0u),
984 _map_coord_idx_lower(box_min[1], 1u), _map_coord_idx_upper(box_max[1], 1u),
985 _map_coord_idx_lower(box_min[2], 2u), _map_coord_idx_upper(box_max[2], 2u));
986 else if(_num_points[1] > 1u)
987 return _sample_box_2d(box_min[0], box_max[0], box_min[1], box_max[1],
988 _map_coord_idx_lower(box_min[0], 0u), _map_coord_idx_upper(box_max[0], 0u),
989 _map_coord_idx_lower(box_min[1], 1u), _map_coord_idx_upper(box_max[1], 1u));
990 else if(_num_points[0] > 1u)
991 return _sample_box_1d(box_min[0], box_max[0],
992 _map_coord_idx_lower(box_min[0], 0u), _map_coord_idx_upper(box_max[0], 0u));
993 else
994 return Real(0);
995 }
996
997 void VoxelMap::_compress_voxel_map_line(const std::vector<int>& mask, const u64 line)
998 {
999 ASSERTM(line < this->_num_lines, "invalid voxel map line");
1000 const u64 n = u64(mask.size());
1001 const u64 off = line * this->_stride_line;
1002 for(u64 i(0); i < n; ++i)
1003 {
1004 _voxel_map[off + (i >> 3)] |= char(mask[i] != 0) << (i & 0x7);
1005 }
1006 }
1007
1008 void VoxelMap::_export_plane_to_bmp(std::ostream& os, u64 plane) const
1009 {
1010 // maximum BMP resolutions seems to be around 30K, but we won't export more than 20K,
1011 // which results in a file size of roughly 50 MB per image
1012 XASSERTM(_num_points[0] <= 20000, "voxel map is too big for BMP export!");
1013 XASSERTM(_num_points[1] <= 20000, "voxel map is too big for BMP export!");
1014 XASSERTM(plane < _num_points[2], "invalid plane index");
1015
1016 typedef std::uint16_t u16;
1017 typedef std::uint32_t u32;
1018
1019 // get dimensions
1020 u32 width = u32(this->_num_points[0]);
1021 u32 height = u32(this->_num_points[1]);
1022
1023 // compute stride (multiple of 4 bytes)
1024 u32 stride = ((width + 31u) & ~31u) >> 3;
1025
1026 // BITMAPFILEHEADER: 14 bytes
1027 // write magic
1028 u16 magic = 0x4D42;
1029 os.write((char*)&magic, 2u);
1030 // write file size
1031 u32 filesize = 62u + height * stride;
1032 os.write((char*)&filesize, 4u);
1033 // write reserved bytes
1034 u32 zeros = 0u;
1035 os.write((char*)&zeros, 4u);
1036 // wite bit offset
1037 u32 offbits = 54u + 2u*4u;
1038 os.write((char*)&offbits, 4u);
1039
1040 // BITMAPINFOHEADER: 40 bytes
1041 // write header size
1042 u32 bisize = 40u;
1043 os.write((char*)&bisize, 4u);
1044 // write width and height
1045 os.write((char*)&width, 4u);
1046 os.write((char*)&height, 4u);
1047 // write plane count
1048 u16 planes = 1;
1049 os.write((char*)&planes, 2u);
1050 // write bit count
1051 u16 bitcount = 1;
1052 os.write((char*)&bitcount, 2u);
1053 // write compression
1054 os.write((char*)&zeros, 4u);
1055 // write image size
1056 u32 size_image = height * stride;
1057 os.write((char*)&size_image, 4u);
1058 // write pixels per meter (X and Y)
1059 u32 px_per_m = 3780;
1060 os.write((char*)&px_per_m, 4u);
1061 os.write((char*)&px_per_m, 4u);
1062 // number of colors
1063 u32 clr_used = 2u;
1064 os.write((char*)&clr_used, 4u);
1065 // number of important colors (WTF?)
1066 os.write((char*)&zeros, 4u);
1067
1068 // write black and white colors
1069 u32 clr_black = 0u, clr_white = ~u32(0);
1070 os.write((char*)&clr_black, 4u);
1071 os.write((char*)&clr_white, 4u);
1072
1073 std::vector<char> linebuffer(stride);
1074 char* oline = linebuffer.data();
1075
1076 // okay, let's loop over all domain nodes
1077 for(u32 i(0); i < height; ++i)
1078 {
1079 const char* iline = &_voxel_map.data()[plane*_stride_plane + i*_stride_line];
1080 for(u32 j(0); j < stride; ++j)
1081 for(int k(0); k < 8; ++k)
1082 (oline[j] <<= 1) |= ((iline[j] >> k) & 1); // all hail to bitshift!
1083 os.write(oline, stride);
1084 }
1085 }
1086
1087 void VoxelMap::_render_plane_to_bmp(std::ostream& os, Index width, Index height, i64 box_min_z, i64 box_max_z) const
1088 {
1089 // maximum BMP resolutions seems to be around 30K, but we won't export more than 20K,
1090 // which results in a file size of roughly 50 MB per image
1091 XASSERTM(width <= 20000, "render width is too big for BMP export!");
1092 XASSERTM(height <= 20000, "render width is too big for BMP export!");
1093 XASSERTM(box_min_z < box_max_z, "invalid depth for voxel map render!");
1094 XASSERTM(box_min_z >= _bbox_min[2], "invalid depth for voxel map render!");
1095 XASSERTM(box_max_z <= _bbox_max[2], "invalid depth for voxel map render!");
1096
1097 typedef std::uint8_t u8;
1098 typedef std::uint16_t u16;
1099 typedef std::uint32_t u32;
1100
1101 // get dimensions
1102 u32 width2 = u32(width);
1103 u32 height2 = u32(height);
1104
1105 // compute stride (multiple of 4 bytes)
1106 u32 stride = ((width2*8 + 31u) & ~31u) >> 3;
1107
1108 // BITMAPFILEHEADER: 14 bytes
1109 // write magic
1110 u16 magic = 0x4D42;
1111 os.write((char*)&magic, 2u);
1112 // write file size
1113 u32 filesize = 54u + height2 * stride + 256u*4u;
1114 os.write((char*)&filesize, 4u);
1115 // write reserved bytes
1116 u32 zeros = 0u;
1117 os.write((char*)&zeros, 4u);
1118 // wite bit offset
1119 u32 offbits = 54u + 256u*4u;
1120 os.write((char*)&offbits, 4u);
1121
1122 // BITMAPINFOHEADER: 40 bytes
1123 // write header size
1124 u32 bisize = 40u;
1125 os.write((char*)&bisize, 4u);
1126 // write width and height
1127 os.write((char*)&width2, 4u);
1128 os.write((char*)&height2, 4u);
1129 // write plane count
1130 u16 planes = 1;
1131 os.write((char*)&planes, 2u);
1132 // write bit count
1133 u16 bitcount = 8;
1134 os.write((char*)&bitcount, 2u);
1135 // write compression
1136 os.write((char*)&zeros, 4u);
1137 // write image size
1138 u32 size_image = height2 * stride;
1139 os.write((char*)&size_image, 4u);
1140 // write pixels per meter (X and Y)
1141 u32 px_per_m = 3780;
1142 os.write((char*)&px_per_m, 4u);
1143 os.write((char*)&px_per_m, 4u);
1144 // number of colors
1145 u32 clr_used = 256u;
1146 os.write((char*)&clr_used, 4u);
1147 // number of important colors (WTF?)
1148 os.write((char*)&zeros, 4u);
1149
1150 // write colors
1151 std::vector<u32> colors(256);
1152 for(u32 i = 0u; i < 256u; ++i)
1153 {
1154 colors[i] = i | (i << 8) | (i << 16);
1155 }
1156 os.write((char*)colors.data(), 256u*4u);
1157
1158 std::vector<u8> linebuffer(stride);
1159 u8* oline = linebuffer.data();
1160
1161 // set up bounding box
1162 std::array<i64, 3u> box_min, box_max;
1163 box_min[2u] = box_min_z;
1164 box_max[2u] = box_max_z;
1165
1166 // okay, let's loop over all domain nodes
1167 for(Index i(0); i < height; ++i)
1168 {
1169 box_min[1u] = i64(_bbox_min[1] + (i * (_bbox_max[1] - _bbox_min[1])) / height);
1170 box_max[1u] = i64(_bbox_min[1] + ((i+1) * (_bbox_max[1] - _bbox_min[1])) / height);
1171 for(Index j(0); j < width; ++j)
1172 {
1173 box_min[0u] = i64(_bbox_min[0] + (j * (_bbox_max[0] - _bbox_min[0])) / width);
1174 box_max[0u] = i64(_bbox_min[0] + ((j+1) * (_bbox_max[0] - _bbox_min[0])) / width);
1175 Real w = _sample_box(box_min, box_max) * 255.0;
1176 oline[j] = (w <= 0.0 ? 0 : (w >= 255.0 ? 255 : u8(w)));
1177 }
1178 os.write((char*)oline, stride);
1179 }
1180 }
1181 } // namespace Geometry
1182} // namespace FEAT
#define ASSERT(expr)
Debug-Assertion macro definition.
Definition: assertion.hpp:229
#define XABORTM(msg)
Abortion macro definition with custom message.
Definition: assertion.hpp:192
#define ASSERTM(expr, msg)
Debug-Assertion macro definition with custom message.
Definition: assertion.hpp:230
#define XASSERT(expr)
Assertion macro definition.
Definition: assertion.hpp:262
#define XASSERTM(expr, msg)
Assertion macro definition with custom message.
Definition: assertion.hpp:263
Binary Stream class.
Communicator class.
Definition: dist.hpp:1349
static void read_common(std::stringstream &stream, const String &filename, const Dist::Comm &comm, int root_rank=0)
Reads a common text file for all ranks.
File-Not-Created exception.
Definition: exception.hpp:223
Wrapper for the CGAL Library.
Definition: cgal.hpp:57
Voxel masker implementation for CGAL wrapper.
Definition: voxel_map.hpp:144
Voxel masker implementation for function parser expression tests.
Definition: voxel_map.hpp:304
helper class for read function result
Definition: voxel_map.hpp:502
helper class for write function result
Definition: voxel_map.hpp:462
Error class for VoxelMap related file errors.
Definition: voxel_map.hpp:37
i64 _map_coord_idx_lower(i64 xyz, std::size_t sdim) const
Maps a unit coordinate to a X-/Y-/Z-index of the lower voxel in the voxel map.
Definition: voxel_map.cpp:565
bool _check_point_nearest(const std::array< i64, 1 > &p) const
Checks the nearest voxel map entry for a given 1D point.
Definition: voxel_map.cpp:631
static constexpr u64 header_size
size of voxel map header in bytes
Definition: voxel_map.hpp:417
Real _sample_box_1d(i64 px0, i64 px1, i64 xidx0, i64 xidx1, i64 yidx=i64(0), i64 zidx=i64(0)) const
Helper function for _sample_box: samples a 1D edge in X line.
Definition: voxel_map.cpp:750
Real _calc_sample_rate(i64 xyz, i64 idx, std::size_t sdim) const
Helper function for _sample_voxel_map_Xd: compute the sample rate in a given dimension.
Definition: voxel_map.cpp:601
std::vector< char > _voxel_map
the actual voxel map; its size may be larger than necessary due to padding
Definition: voxel_map.hpp:545
void export_to_bmp(const String &filename_prefix) const
Exports the voxel map to a sequence of monochrome BMP image files.
Definition: voxel_map.cpp:456
bool _check_point(i64 xidx, i64 yidx, i64 zidx) const
Helper function for _sample_point: checks a single voxel point value.
Definition: voxel_map.cpp:620
WriteResult write(const String &filename, const u64 compress_block_size=128u, const int compress_level=9) const
Writes the voxel map into a file that can later be read in.
Definition: voxel_map.cpp:186
void render_to_bmp(const String &filename_prefix, Index width, Index height, Index depth) const
Renders the voxel map to a sequence of gray-scale BMP image files.
Definition: voxel_map.cpp:481
void _export_plane_to_bmp(std::ostream &os, u64 plane) const
Exports a single plane of the voxel map to a monochrome BMP image file.
Definition: voxel_map.cpp:1008
i64 _map_coord_idx_upper(i64 xyz, std::size_t sdim) const
Maps a unit coordinate to a X-/Y-/Z-index of the upper voxel in the voxel map.
Definition: voxel_map.cpp:580
Real _sample_point_1d_y(i64 py, i64 xidx, i64 yidx, i64 zidx=i64(0)) const
Helper function for _sample_point: samples a 1D edge in Y-parallel line.
Definition: voxel_map.cpp:706
Real _sample_box_2d(i64 px0, i64 px1, i64 py0, i64 py1, i64 xidx0, i64 xidx1, i64 yidx0, i64 yidx1, i64 zidx=i64(0)) const
Helper function for _sample_box: samples a 2D rectangle in XY plane.
Definition: voxel_map.cpp:843
std::array< u64, 3u > _num_points
number of points in each dimension
Definition: voxel_map.hpp:535
Real _sample_box_3d(i64 px0, i64 px1, i64 py0, i64 py1, i64 pz0, i64 pz1, i64 xidx0, i64 xidx1, i64 yidx0, i64 yidx1, i64 zidx0, i64 zidx1) const
Helper function for _sample_box: samples a 3D cuboid in XYZ volume.
Definition: voxel_map.cpp:900
void _compress_voxel_map_line(const std::vector< int > &mask, const u64 line)
Compresses a voxel map line into the voxel map.
Definition: voxel_map.cpp:997
u64 _compute_domain_coverage() const
Computes the domain coverage of the voxel map.
Definition: voxel_map.cpp:518
void export_plane_to_bmp(const String &filename, Real z_coord) const
Exports a single plane of the voxel map to a monochrome BMP image file.
Definition: voxel_map.cpp:469
void _compute_voxel_map(const Dist::Comm &comm, VoxelMasker< CoordType, dim_ > &masker, bool gather_to_all)
Computes the voxel map for the domain.
Definition: voxel_map.hpp:1623
static constexpr u64 magic_number
voxel map magic number
Definition: voxel_map.hpp:414
bool check_point(u64 ix, u64 iy=0u, u64 iz=0u) const
Returns the value of a specific voxel point in the voxel map.
Definition: voxel_map.hpp:1006
Real get_bounding_box_volume() const
Returns the volume of the 1D/2D/3D voxel map bounding box.
Definition: voxel_map.cpp:507
u64 _num_planes
number of planes; 2D = 1; 3D = num_points[3]
Definition: voxel_map.hpp:543
std::uint64_t u64
unsigned 64-bit integer type
Definition: voxel_map.hpp:411
Real _sample_point_1d_z(i64 pz, i64 xidx, i64 yidx, i64 zidx) const
Helper function for _sample_point: samples a 1D edge in Z-parallel line.
Definition: voxel_map.cpp:713
Index get_num_voxels() const
Returns the total number of voxels in the voxel map.
Definition: voxel_map.hpp:658
void set_resolution(Real max_res)
Sets the resolution for the voxel map, i.e. the maximum voxel size in each dimension.
Definition: voxel_map.cpp:127
u64 _stride_line
stride of a single voxel line in X dimension
Definition: voxel_map.hpp:537
void compute_map_from_formula_3d(const Dist::Comm &comm, const String &formula, bool gather_to_all=true)
Creates the voxel map based on a 3D formula by utilizing MPI parallelization.
Definition: voxel_map.cpp:149
void set_num_points(Index num_x, Index num_y, Index num_z=0u)
Sets the number of points of the voxel map in each dimension.
Definition: voxel_map.cpp:108
virtual ~VoxelMap()
virtual destructor
Definition: voxel_map.cpp:75
void render_plane_to_bmp(const String &filename, Index width, Index height, Real z_min, Real z_max) const
Renders a plane range of voxel map to a single gray-scale BMP image file.
Definition: voxel_map.cpp:495
std::int64_t i64
signed 64-bit integer type
Definition: voxel_map.hpp:408
Real _sample_point_1d_x(i64 px, i64 xidx, i64 yidx=i64(0), i64 zidx=i64(0)) const
Helper function for _sample_point: samples a 1D edge in X-parallel line.
Definition: voxel_map.cpp:699
void compute_map_from_off_3d(const Dist::Comm &comm, const String &filename, bool invert, bool gather_to_all=true)
Creates the voxel map based on a 3D OFF model handled by CGAL by utilizing MPI parallelization.
Definition: voxel_map.cpp:162
u64 _stride_plane
stride of a single plane in XY dimensions
Definition: voxel_map.hpp:539
u64 _coverage
the coverage of the domain, relative to unit size
Definition: voxel_map.hpp:549
void set_bounding_box_2d(Real x_min, Real x_max, Real y_min, Real y_max)
Sets the bounding box for a 2D voxel map.
Definition: voxel_map.cpp:79
Real _sample_point_2d(i64 px, i64 py, i64 xidx, i64 yidx, i64 zidx=i64(0)) const
Helper function for _sample_point: samples a 2D square in XY-parallel plane.
Definition: voxel_map.cpp:720
static constexpr i64 unit_size
size of a real unit (meter) in our internal units (nanometers)
Definition: voxel_map.hpp:420
bool _out_of_bounds_value
the out-of-bounds-value for the voxel map
Definition: voxel_map.hpp:547
ReadResult read(const Dist::Comm &comm, const String &filename)
Reads a voxel map from a file.
Definition: voxel_map.cpp:322
std::array< i64, 3u > _bbox_min
bounding box dimensions of domain
Definition: voxel_map.hpp:533
bool _check_box(const std::array< u64, 1 > &box_min, const std::array< u64, 1 > &box_max) const
Checks whether a 1D box contains at least one active voxel.
Definition: voxel_map.cpp:659
void compute_map_from_formula_2d(const Dist::Comm &comm, const String &formula, bool gather_to_all=true)
Creates the voxel map based on a 2D formula by utilizing MPI parallelization.
Definition: voxel_map.cpp:136
Real _sample_point(const std::array< i64, 1 > &p) const
Samples the voxel map for a given 1D point by multi-linear interpolation.
Definition: voxel_map.cpp:734
int _create_stage
current creation stage
Definition: voxel_map.hpp:531
u64 _map_coord_idx_nearest(i64 xyz, std::size_t sdim) const
Maps a unit coordinate to a X-/Y-/Z-index of the closest voxel in the voxel map.
Definition: voxel_map.cpp:535
VoxelMap()
standard constructor
Definition: voxel_map.cpp:23
Real _sample_point_3d(i64 px, i64 py, i64 pz, i64 xidx, i64 yidx, i64 zidx) const
Helper function for _sample_point: samples a 3D cube in XYZ volume.
Definition: voxel_map.cpp:727
void set_bounding_box_3d(Real x_min, Real x_max, Real y_min, Real y_max, Real z_min, Real z_max)
Sets the bounding box for a 3D voxel map.
Definition: voxel_map.cpp:92
void _render_plane_to_bmp(std::ostream &os, Index width, Index height, i64 box_min_z, i64 box_max_z) const
Renders a plane range of voxel map to a single gray-scale BMP image file.
Definition: voxel_map.cpp:1087
static u64 calc_line_stride(u64 num_x)
ensure that we're not compiling on some freak architecture...
Definition: voxel_map.hpp:448
Real _sample_box(const std::array< i64, 1 > &box_min, const std::array< i64, 1 > &box_max) const
Samples a 1D box of the voxel map.
Definition: voxel_map.cpp:957
u64 _num_lines
number of lines; 2D = num_points[2]; 3D: = num_points[2] * num_points[3]
Definition: voxel_map.hpp:541
String class implementation.
Definition: string.hpp:46
String pad_front(size_type len, char c=' ') const
Pads the front of the string up to a desired length.
Definition: string.hpp:392
std::uint64_t u64
unsigned 64-bit integer type
Definition: voxel_map.cpp:21
@ other
generic/other permutation strategy
std::int64_t i64
signed 64-bit integer type
Definition: voxel_map.cpp:19
T_ abs(T_ x)
Returns the absolute value.
Definition: math.hpp:275
T_ sqr(T_ x)
Returns the square of a value.
Definition: math.hpp:95
T_ min(T_ a, T_ b)
Returns the minimum of two values.
Definition: math.hpp:123
T_ cub(T_ x)
Returns the cube of a value.
Definition: math.hpp:109
T_ max(T_ a, T_ b)
Returns the maximum of two values.
Definition: math.hpp:137
FEAT namespace.
Definition: adjactor.hpp:12
double Real
Real data type.
String stringify(const T_ &item)
Converts an item into a String.
Definition: string.hpp:944
std::uint64_t Index
Index data type.
header structure of voxel map file header
Definition: voxel_map.hpp:424