#pragma once #include #include #include #include #include #include #include "streaming_mesh.h" namespace cgv { namespace media { namespace mesh { /// information stored per cell template struct cell_info { cgv::math::qem Q; cgv::math::fvec center; int count; }; /** data structure for the information that is cached per volume slice */ template struct dc_slice_info { unsigned int resx, resy; std::vector values; std::vector flags; std::vector indices; typedef cgv::math::qem qem_type; typedef cgv::math::fvec pnt_type; std::vector > cell_infos; /// dc_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(n); cell_infos.resize(n); } /// void init() { int n = resx*resy; for (int i=0; iiso_value; } /// const int& index(int x, int y) const { return indices[y*resx+x]; } int& index(int x, int y) { return indices[y*resx+x]; } /// const qem_type& get_qem(int x, int y) const { return cell_infos[y*resx+x].Q; } qem_type& ref_qem(int x, int y) { return cell_infos[y*resx+x].Q; } /// int count(int x, int y) const { return cell_infos[y*resx+x].count; } int& count(int x, int y) { return cell_infos[y*resx+x].count; } /// const pnt_type& center(int x, int y) const { return cell_infos[y*resx+x].center; } pnt_type& center(int x, int y) { return cell_infos[y*resx+x].center; } /// const cell_info& info(int x, int y) const { return cell_infos[y*resx+x]; } cell_info& info(int x, int y) { return cell_infos[y*resx+x]; } }; /// class used to perform the marching cubes algorithm template class dual_contouring : public streaming_mesh { public: typedef streaming_mesh base_type; /// points must have three components typedef cgv::math::fvec pnt_type; /// vectors must have three components typedef cgv::math::fvec vec_type; /// qem type must have dimension three typedef cgv::math::qem qem_type; /// qem type must have dimension three typedef cell_info cell_info_type; private: pnt_type p, minp; unsigned int resx, resy, resz; vec_type d; T iso_value; protected: const cgv::math::v3_func& func; X epsilon; unsigned int max_nr_iters; X consistency_threshold; public: /// construct dual contouring object dual_contouring(const cgv::math::v3_func& _func, streaming_mesh_callback_handler* _smcbh, const X& _consistency_threshold = 0.01f, unsigned int _max_nr_iters = 10, const X& _epsilon = 1e-6f) : func(_func), max_nr_iters(_max_nr_iters), consistency_threshold(_consistency_threshold), epsilon(_epsilon) { base_type::set_callback_handler(_smcbh); } /// construct a new vertex on an edge void compute_cell_vertex(dc_slice_info *info_ptr, int i, int j) { if (info_ptr->count(i,j) == 0) { info_ptr->index(i,j) = -1; return; } pnt_type p_ref = info_ptr->center(i,j) / info_ptr->count(i,j); pnt_type q = (X*)info_ptr->get_qem(i,j).minarg(p_ref.to_vec(), 0.1, d.length()); info_ptr->index(i,j) = this->new_vertex(q); } /// construct a quadrilateral void generate_quad(unsigned int vi, unsigned int vj, unsigned int vk, unsigned int vl, bool reorient) { if (reorient) base_type::new_quad(vi,vl,vk,vj); else base_type::new_quad(vi,vj,vk,vl); } /// void compute_edge_point(const T& _v_1, const T& _v_2, int e, const pnt_type& p, const vec_type& d, pnt_type& q, vec_type& n) { X de = d(e); T v_1 = _v_1; T v_2 = _v_2; pnt_type p_end = p; q = p; unsigned int nr_iters = 0; bool finished; X ref = consistency_threshold*fabs(v_2-v_1); do { finished = false; // compute point on edge X alpha; if (fabs(v_2-v_1) > epsilon) { finished = true; alpha = (X)(iso_value-v_1)/(v_2-v_1); } else alpha = (X) 0.5; q(e) = p_end(e) - (1-alpha)*de; // compute normal at point n = vec_type(func.evaluate_gradient(q.to_vec())); // stop if maximum number of iterations reached if (++nr_iters >= max_nr_iters) break; // check if gradient is in accordance with value difference if (finished) { if (alpha < 0 || alpha > 1) { std::cout << "invalid alpha = " << alpha << std::endl; } X f_e = (v_2 - v_1) / de; if (fabs(f_e - n(e)) > consistency_threshold*f_e) { X v = func.evaluate(q.to_vec()); if (fabs(v-iso_value) > ref) { if ((v > iso_value) == (v_2 > iso_value)) { de *= alpha; v_2 = v; p_end(e) = q(e); } else { v_1 = v; de *= 1-alpha; } finished = false; } } } } while (!finished); if (nr_iters > 15) std::cout << "not converged after " << nr_iters << " iterations" << std::endl; if (n.length() < 1e-6) std::cout << "degenerate gradient" << n << std::endl; else n.normalize(); } /// construct plane through edge and add it to the incident qems void process_edge_plane(const T& v_1, const T& v_2, int e, cell_info_type* C1, cell_info_type* C2, cell_info_type* C3, cell_info_type* C4) { pnt_type q; vec_type n; compute_edge_point(v_1, v_2, e, p, d, q, n); // construct qem qem_type Q(q.to_vec(),n.to_vec()); // add qem to incident cells if (C1) { if (C1->count == 0) { C1->Q = Q; C1->center = q; } else { C1->Q += Q; C1->center += q; } ++C1->count; } if (C2) { if (C2->count == 0) { C2->Q = Q; C2->center = q; } else { C2->Q += Q; C2->center += q; } ++C2->count; } if (C3) { if (C3->count == 0) { C3->Q = Q; C3->center = q; } else { C3->Q += Q; C3->center += q; } ++C3->count; } if (C4) { if (C4->count == 0) { C4->Q = Q; C4->center = q; } else { C4->Q += Q; C4->center += q; } ++C4->count; } } /// process a slice void process_slice(dc_slice_info *prev_info_ptr, dc_slice_info *info_ptr) { unsigned int i,j; info_ptr->init(); for (j = 0, p(1) = minp(1); j < resy; ++j, p(1) += d(1)) for (i = 0, p(0) = minp(0); i < resx; ++i, p(0)+=d(0)) { // eval function on slice info_ptr->set_value(i,j,func.evaluate(p.to_vec()),iso_value); // process slice internal edges if (i > 0 && info_ptr->flag(i-1,j) != info_ptr->flag(i,j)) process_edge_plane(info_ptr->value(i-1,j), info_ptr->value(i,j), 0, &info_ptr->info(i-1,j), j > 0 ? &info_ptr->info(i-1,j-1) : 0, prev_info_ptr ? &prev_info_ptr->info(i-1,j) : 0, (j > 0 && prev_info_ptr) ? &prev_info_ptr->info(i-1,j-1) : 0); if (j > 0 && info_ptr->flag(i,j-1) != info_ptr->flag(i,j)) process_edge_plane(info_ptr->value(i,j-1), info_ptr->value(i,j), 1, &info_ptr->info(i,j-1), i > 0 ? &info_ptr->info(i-1,j-1) : 0, prev_info_ptr ? &prev_info_ptr->info(i,j-1) : 0, (i > 0 && prev_info_ptr) ? &prev_info_ptr->info(i-1,j-1) : 0); } } /// void process_slab(dc_slice_info *info_ptr_1, dc_slice_info *info_ptr_2) { unsigned int i,j; for (j = 0, p(1) = minp(1); j < resy; ++j, p(1) += d(1)) for (i = 0, p(0) = minp(0); i < resx; ++i, p(0)+=d(0)) { // process slab edges if (info_ptr_1->flag(i,j) != info_ptr_2->flag(i,j)) process_edge_plane(info_ptr_1->value(i,j), info_ptr_2->value(i,j), 2, &info_ptr_1->info(i,j), j > 0 ? &info_ptr_1->info(i,j-1) : 0, i > 0 ? &info_ptr_1->info(i-1,j) : 0, (i > 0 && j > 0) ? &info_ptr_1->info(i-1,j-1) : 0); // compute cell vertices if (i>0 && j>0) compute_cell_vertex(info_ptr_1, i-1,j-1); } // generate the quads of inner edges inside the slab for (j = 1, p(1) = minp(1)+d(1); j < resy-1; ++j, p(1) += d(1)) for (i = 1, p(0) = minp(0)+d(0); i < resx-1; ++i, p(0)+=d(0)) if (info_ptr_1->flag(i,j) != info_ptr_2->flag(i,j)) generate_quad(info_ptr_1->index(i,j), info_ptr_1->index(i-1,j), info_ptr_1->index(i-1,j-1), info_ptr_1->index(i,j-1), info_ptr_1->flag(i,j)); } /// void generate_slice_quads(dc_slice_info *info_ptr_1, dc_slice_info *info_ptr_2) { unsigned int i,j; for (j = 1, p(1) = minp(1)+d(1); j < resy-1; ++j, p(1) += d(1)) for (i = 1, p(0) = minp(0)+d(0); i < resx-1; ++i, p(0)+=d(0)) { if (info_ptr_2->flag(i-1,j) != info_ptr_2->flag(i,j)) generate_quad(info_ptr_1->index(i-1,j), info_ptr_2->index(i-1,j), info_ptr_2->index(i-1,j-1), info_ptr_1->index(i-1,j-1), info_ptr_2->flag(i-1,j)); if (info_ptr_2->flag(i,j-1) != info_ptr_2->flag(i,j)) generate_quad(info_ptr_1->index(i,j-1), info_ptr_1->index(i-1,j-1), info_ptr_2->index(i-1,j-1), info_ptr_2->index(i,j-1), info_ptr_2->flag(i,j-1)); } } /// extract iso surface and send quads to dual contouring handler void extract(const T& _iso_value, const axis_aligned_box& box, unsigned int _resx, unsigned int _resy, unsigned int _resz, bool show_progress = false) { // prepare private members resx = _resx; resy = _resy; resz = _resz; minp = 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 three slice infos dc_slice_info slice_info_1(resx,resy), slice_info_2(resx,resy), slice_info_3(resx,resy); dc_slice_info *slice_info_ptrs[3] = { &slice_info_1, &slice_info_2, &slice_info_3 }; // iterate through all slices unsigned int nr_vertices[4] = { 0, 0, 0, 0 }; unsigned int k, n; process_slice(0, slice_info_ptrs[0]); p(2) += d(2); process_slice(slice_info_ptrs[0], slice_info_ptrs[1]); process_slab(slice_info_ptrs[0], slice_info_ptrs[1]); p(2) += d(2); // show progression if (show_progress) { prog.step(); prog.step(); } for (k=2; k *info_ptr_0 = slice_info_ptrs[(k-2)%3]; dc_slice_info *info_ptr_1 = slice_info_ptrs[(k-1)%3]; dc_slice_info *info_ptr_2 = slice_info_ptrs[k%3]; process_slice(info_ptr_1, info_ptr_2); process_slab(info_ptr_1, info_ptr_2); generate_slice_quads(info_ptr_0, info_ptr_1); n = base_type::get_nr_vertices()-n; nr_vertices[k%4] = n; n = nr_vertices[(k+3)%4]; if (n > 0) base_type::drop_vertices(n); // show progression if (show_progress) prog.step(); } } }; } } }