CGII/framework/include/cgv/media/mesh/marching_cubes.h

289 lines
9.1 KiB
C
Raw Normal View History

2018-05-17 13:50:03 +00:00
#pragma once
#include <vector>
#include <deque>
#include <cgv/utils/progression.h>
#include <cgv/math/fvec.h>
#include <cgv/math/mfunc.h>
#include <cgv/media/axis_aligned_box.h>
#include <cgv/media/mesh/streaming_mesh.h>
#include <cgv/media/lib_begin.h>
namespace cgv {
namespace media {
namespace mesh {
extern CGV_API int get_nr_cube_triangles(int idx);
extern CGV_API void put_cube_triangle(int idx, int t, int& vi, int& vj, int& vk);
/** data structure for the information that is cached per volume slice */
template <typename T>
struct slice_info
{
unsigned int resx, resy;
std::vector<T> values;
std::vector<bool> flags;
std::vector<int> indices;
///
slice_info(unsigned int _resx, unsigned int _resy) : resx(_resx), resy(_resy) {
unsigned int n = resx*resy;
values.resize(n);
flags.resize(n);
indices.resize(4 * n);
}
///
void init() {
int n = resx*resy;
for (int i = 0; i < 4 * n; ++i)
indices[i] = -1;
}
///
bool flag(int x, int y) const { return flags[y*resx + x]; }
///
int get_bit_code(int x, int y, int step = 1) const {
int i = y*resx + x;
return (flags[i] ? 1 : 0) + (flags[i + step] ? 2 : 0) +
(flags[i + step*(resx + 1)] ? 4 : 0) + (flags[i + step*resx] ? 8 : 0);
}
///
const T& value(int x, int y) const { return values[y*resx + x]; }
T& value(int x, int y) { return values[y*resx + x]; }
/// set value and flag at once
void set_value(int x, int y, T value, T iso_value) {
int i = y*resx + x;
values[i] = value;
flags[i] = value > iso_value;
}
///
const int& index(int x, int y, int e) const { return indices[4 * (y*resx + x) + e]; }
int& index(int x, int y, int e) { return indices[4 * (y*resx + x) + e]; }
///
const int& snap_index(int x, int y) const { return indices[4 * (y*resx + x) + 3]; }
int& snap_index(int x, int y) { return indices[4 * (y*resx + x) + 3]; }
};
/// class used to perform the marching cubes algorithm
template <typename X, typename T>
class marching_cubes_base : public streaming_mesh<X>
{
public:
typedef streaming_mesh<X> base_type;
/// points must have three components
typedef cgv::math::fvec<X,3> pnt_type;
/// vectors must have three components
typedef cgv::math::fvec<X,3> vec_type;
private:
pnt_type p;
vec_type d;
T iso_value;
protected:
X epsilon;
X grid_epsilon;
public:
/// construct marching cubes object
marching_cubes_base(streaming_mesh_callback_handler* _smcbh,
const X& _grid_epsilon = 0.01f,
const X& _epsilon = 1e-6f) : epsilon(_epsilon), grid_epsilon(_grid_epsilon)
{
base_type::set_callback_handler(_smcbh);
}
/// construct a new vertex on an edge
void construct_vertex(slice_info<T> *info_ptr_1, int i_1, int j_1, int e,
slice_info<T> *info_ptr_2, int i_2, int j_2)
{
// read values at edge ends
T v_1 = info_ptr_1->value(i_1, j_1);
T v_2 = info_ptr_2->value(i_2, j_2);
// from values compute affin location
X f = (fabs(v_2 - v_1) > epsilon) ? (X)(iso_value - v_1) / (v_2 - v_1) : (X) 0.5;
// check whether to snap to edge start
int vi = base_type::get_nr_vertices();
pnt_type q = p;
if (f < grid_epsilon) {
int vj = info_ptr_1->snap_index(i_1, j_1);
if (vj != -1) {
info_ptr_1->index(i_1, j_1, e) = vj;
return;
}
info_ptr_1->snap_index(i_1, j_1) = vi;
q(e) -= d(e);
}
else if (1 - f < grid_epsilon) {
int vj = info_ptr_2->snap_index(i_2, j_2);
if (vj != -1) {
info_ptr_1->index(i_1, j_1, e) = vj;
return;
}
info_ptr_2->snap_index(i_2, j_2) = vi;
}
else
q(e) -= (1 - f)*d(e);
info_ptr_1->index(i_1, j_1, e) = vi;
this->new_vertex(q);
}
/// extract iso surface and send triangles to marching cubes handler
template <typename Eval, typename Valid>
void extract_impl(const T& _iso_value,
const axis_aligned_box<X, 3>& box,
unsigned int resx, unsigned int resy, unsigned int resz,
const Eval& eval, const Valid& valid, bool show_progress = false)
{
// prepare private members
p = box.get_min_pnt();
d = box.get_extent();
d(0) /= (resx - 1); d(1) /= (resy - 1); d(2) /= (resz - 1);
iso_value = _iso_value;
// prepare progression
cgv::utils::progression prog;
if (show_progress) prog.init("extraction", resz, 10);
// construct two slice infos
slice_info<T> slice_info_1(resx, resy), slice_info_2(resx, resy);
slice_info<T> *slice_info_ptrs[2] = { &slice_info_1, &slice_info_2 };
// iterate through all slices
unsigned int nr_vertices[3] = { 0, 0, 0 };
unsigned int i, j, k, n;
for (k = 0; k < resz; ++k, p(2) += d(2)) {
n = (int)base_type::get_nr_vertices();
// evaluate function on next slice and construct slice interior vertices
slice_info<T> *info_ptr = slice_info_ptrs[k & 1];
info_ptr->init();
for (j = 0, p(1) = box.get_min_pnt()(1); j < resy; ++j, p(1) += d(1))
for (i = 0, p(0) = box.get_min_pnt()(0); i < resx; ++i, p(0) += d(0)) {
T v = eval(i, j, k, p);
info_ptr->set_value(i, j, v, iso_value);
if (valid(v)) {
if (i > 0 && info_ptr->flag(i - 1, j) != info_ptr->flag(i, j) && valid(info_ptr->value(i - 1, j)))
construct_vertex(info_ptr, i - 1, j, 0, info_ptr, i, j);
if (j > 0 && info_ptr->flag(i, j - 1) != info_ptr->flag(i, j) && valid(info_ptr->value(i, j - 1)))
construct_vertex(info_ptr, i, j - 1, 1, info_ptr, i, j);
}
}
// show progression
if (show_progress)
prog.step();
// if this is the first considered slice, construct the next one
if (k != 0) {
// get info of previous slice
slice_info<T> *prev_info_ptr = slice_info_ptrs[1 - (k & 1)];
// construct vertices on edges between previous and new slice
for (j = 0, p(1) = box.get_min_pnt()(1); j < resy; ++j, p(1) += d(1))
for (i = 0, p(0) = box.get_min_pnt()(0); i < resx; ++i, p(0) += d(0))
if (prev_info_ptr->flag(i, j) != info_ptr->flag(i, j) && valid(prev_info_ptr->value(i, j)) && valid(info_ptr->value(i, j)))
construct_vertex(prev_info_ptr, i, j, 2, info_ptr, i, j);
// construct triangles
for (j = 0; j < resy - 1; ++j) {
for (i = 0; i < resx - 1; ++i) {
// compute the bit index for the current cube
int idx = prev_info_ptr->get_bit_code(i, j) +
16 * info_ptr->get_bit_code(i, j);
// skip empty cubes
if (idx == 0 || idx == 255)
continue;
// set edge vertices
int vis[12] = {
prev_info_ptr->index(i, j, 0),
prev_info_ptr->index(i + 1, j, 1),
prev_info_ptr->index(i, j + 1, 0),
prev_info_ptr->index(i, j, 1),
info_ptr->index(i, j, 0),
info_ptr->index(i + 1, j, 1),
info_ptr->index(i, j + 1, 0),
info_ptr->index(i, j, 1),
prev_info_ptr->index(i, j, 2),
prev_info_ptr->index(i + 1, j, 2),
prev_info_ptr->index(i, j + 1, 2),
prev_info_ptr->index(i + 1, j + 1, 2)
};
// lookup triangles and construct them
int n = get_nr_cube_triangles(idx);
for (int t = 0; t < n; ++t) {
int vi, vj, vk;
put_cube_triangle(idx, t, vi, vj, vk);
vi = vis[vi];
vj = vis[vj];
vk = vis[vk];
if (vi == -1 || vj == -1 || vk == -1)
continue;
if ((vi != vj) && (vi != vk) && (vj != vk))
base_type::new_triangle(vk, vj, vi);
}
}
}
}
n = (int)base_type::get_nr_vertices() - n;
nr_vertices[k % 3] = n;
n = nr_vertices[(k + 2) % 3];
if (n > 0)
base_type::drop_vertices(n);
}
}
};
template <typename T>
struct always_valid
{
bool operator () (const T& v) const { return true; }
};
#include <limits>
template <typename T>
struct not_max_valid
{
bool operator () (const T& v) const { return v != std::numeric_limits<T>::max(); }
};
template <typename T>
struct not_nan_valid
{
bool operator () (const T& v) const { return !std::isnan(v); }
};
/// class used to perform the marching cubes algorithm
template <typename X, typename T>
class marching_cubes : public marching_cubes_base<X, T>
{
public:
// GCC does not examine the base class scope for templates,
// so they must be explicitely redefined
typedef typename marching_cubes_base<X, T>::base_type base_type;
/// points must have three components
typedef typename marching_cubes_base<X, T>::pnt_type pnt_type;
/// vectors must have three components
typedef typename marching_cubes_base<X, T>::vec_type vec_type;
const cgv::math::v3_func<X, T>& func;
/// construct marching cubes object
marching_cubes(const cgv::math::v3_func<X, T>& _func,
streaming_mesh_callback_handler* _smcbh,
const X& _grid_epsilon = 0.01f,
const X& _epsilon = 1e-6f) : marching_cubes_base<X, T>(_smcbh, _grid_epsilon, _epsilon), func(_func)
{
}
T operator () (unsigned i, unsigned j, unsigned k, const pnt_type& p) const {
return func.evaluate(p.to_vec());
}
void extract(const T& _iso_value,
const axis_aligned_box<X, 3>& box,
unsigned int resx, unsigned int resy, unsigned int resz,
bool show_progress = false)
{
always_valid<T> valid;
this->extract_impl(_iso_value, box, resx, resy, resz, *this, valid, show_progress);
}
};
}
}
}
#include <cgv/config/lib_end.h>