Rev 489 | Rev 506 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed
#include "Manifold.h"
#include <iostream>
#include <iterator>
#include <vector>
#include <map>
#include <CGLA/Vec3f.h>
#include <Geometry/TriMesh.h>
#include "VertexHandle.h"
#include "FaceHandle.h"
#include "HalfEdgeHandle.h"
#include "VertexCirculator.h"
#include "FaceCirculator.h"
namespace HMesh
{
using std::vector;
using std::back_insert_iterator;
using std::map;
using std::cout;
using std::endl;
using Geometry::TriMesh;
using namespace CGLA;
// brief code used for manifold and edge creation
namespace
{
class EdgeKey
{
public:
EdgeKey(size_t va, size_t vb)
{
if(va < vb){
v0 = va;
v1 = vb;
}
else{
v0 = vb;
v1 = va;
}
}
bool operator<(const EdgeKey& k2) const
{
if(v0 < k2.v0){
return true;
}
else if(v0 > k2.v0){
return false;
}
else{
return v1 < k2.v1;
}
}
private:
int v0;
int v1;
};
struct Edge
{
HIDX h0;
HIDX h1;
size_t count;
Edge() : h0(NULL_HALFEDGE_IDX), h1(NULL_HALFEDGE_IDX), count(0){}
};
}
// build functions
void Manifold::build(const TriMesh& mesh)
{
// A vector of 3's - used to tell build how many indices each face consists of
vector<int> faces(mesh.geometry.no_faces(), 3);
build( static_cast<int>(mesh.geometry.no_vertices()),
reinterpret_cast<const float*>(&mesh.geometry.vertex(0)),
static_cast<int>(faces.size()),
&faces[0],
reinterpret_cast<const int*>(&mesh.geometry.face(0)));
}
template<typename float_type, typename int_type>
void Manifold::build( int_type no_vertices,
const float_type* vertvec,
int_type no_faces,
const int_type* facevec,
const int_type* indices,
const int* touch)
{
// clean up the manifold for the build
clear();
// allocate space
vertices.resize(no_vertices);
faces.resize(no_faces);
// create vertices corresponding to positions stored in vertvec
for(size_t i = 0; i < no_vertices; ++i){
Vec3f v;
v[0] = vertvec[i*3];
v[1] = vertvec[i*3+1];
v[2] = vertvec[i*3+2];
vertices[i].pos = v;
}
// update touched values if the optional touch array was passed to build
if(touch){
for(size_t i = 0; i < no_vertices; ++i){
vertices[i].touched = touch[i];
}
}
//map over the created edges - needed to preserve edge uniqueness
typedef map<EdgeKey, Edge> EdgeMap;
EdgeMap edge_map;
// counter that jumps between faces in indices vector
size_t n = 0;
// create faces correspponding to faces stored in facevec
for(size_t i = 0; i < no_faces; ++i){
//amount of vertices in current face
size_t N = facevec[i];
vector<HalfEdgeHandle> fh;
//each face indice results corresponds to 1 edge, 2 halfedges
for(size_t j = 0; j < N; ++j){
// each iteration uses two indices from the face
VIDX v_idx0 = indices[j+n];
VIDX v_idx1 = indices[(j+1)%N+n];
// ensure indice integrity
assert(v_idx0 < no_vertices);
assert(v_idx1 < no_vertices);
// create key and search map for edge
EdgeKey ek(v_idx0, v_idx1);
EdgeMap::iterator em_iter = edge_map.find(ek);
// current edge has not been created
if(em_iter == edge_map.end()){
HalfEdgeHandle h0 = add_halfedge(HalfEdge());
HalfEdgeHandle h1 = add_halfedge(HalfEdge());
// create edge for map
Edge e;
e.h0 = h0.get_idx();
e.h1 = h1.get_idx();
e.count = 1;
// glue operation: 1 edge = 2 glued halfedges
glue(h0, h1);
// update vertices with their outgoing halfedges
vertices[v_idx0].out_idx = e.h0;
vertices[v_idx1].out_idx = e.h1;
// update halfedges with the vertices they point to
halfedges[e.h0].vert_idx = v_idx1;
halfedges[e.h1].vert_idx = v_idx0;
// update map
edge_map[ek] = e;
// update vector of halfedges belonging to current face
fh.push_back(h0);
}
else{
// update current face with halfedge from edge
fh.push_back(HalfEdgeHandle(*this, em_iter->second.h1));
// asserting that a halfedge is visited exactly twice;
// once for each face on either side of the edge.
em_iter->second.count++;
assert(em_iter->second.count == 2);
}
}
for(size_t j = 0; j < N; ++j){
// update halfedge with face
fh[j].get().face_idx = i;
// link operation: link two halfedges in the same face
link(fh[j], fh[(j+1)%N]);
}
//update face with the first halfedge created
faces[i].last_idx = fh[0].get_idx();
// step to first indice of next face
n += N;
}
// test for unused vertices
for(size_t i = 0; i < no_vertices; ++i){
if(vertices[i].out_idx == NULL_HALFEDGE_IDX){
vertices.remove(i);
}
}
// boundary check while avoiding unused vertices
for(size_t i = 0; i < no_vertices; ++i){
if(vertices[i].out_idx != NULL_HALFEDGE_IDX){
check_boundary_consistency(VertexHandle(*this, i));
}
}
}
bool Manifold::is_valid()
{
// Verify components of halfedges
for(HalfEdgeHandle h = halfedges_begin(); h != halfedges_end(); ++h){
if(h.get().face_idx == NULL_FACE_IDX){
cout << "Halfedge [" << h.get_idx() << "] lacks face" << endl;
return false;
}
if(h.get().vert_idx == NULL_VERTEX_IDX){
cout << "Halfedge [" << h.get_idx() << "] lacks vert" << endl;
return false;
}
if(h.get().next_idx == NULL_HALFEDGE_IDX){
cout << "Halfedge [" << h.get_idx() << "] lacks next" << endl;
return false;
}
if(h.get().prev_idx == NULL_HALFEDGE_IDX){
cout << "Halfedge [" << h.get_idx() << "] lacks prev" << endl;
return false;
}
if(h.get().opp_idx == NULL_HALFEDGE_IDX){
cout << "Halfedge [" << h.get_idx() << "] lacks opp" << endl;
return false;
}
}
// Verify components of vertices
for(VertexHandle v = vertices_begin(); v != vertices_end(); ++v){
vector<VIDX> link;
for(VertexCirculator vc(v); !vc.end(); ++vc){
// test halfedges around v
if(vc.halfedge() == NULL_HALFEDGE_HANDLE){
cout << "Vertex [" << v.get_idx() << "] circulation produced null halfedge at step " << vc.no_steps() << endl;
return false;
}
VIDX ring_v = vc.vertex().get_idx();
// test one-ring for multiple occurences of vertex
if(find(link.begin(), link.end(), ring_v) != link.end()){
cout << "Vertex [" << ring_v << "] appears two times in the one-ring of vertex [" << v.get_idx() << "]" << endl;
return false;
}
link.push_back(ring_v);
// test for infinite loop around vertex
if(vc.no_steps() > vertices.size()){
cout << "Vertex [" << v.get_idx() << "] loop contains more vertices than manifold" << endl;
return false;
}
}
// test one_ring size for boundary consistency
if(link.size() == 2){
if(!is_boundary(v)){
cout << "Vertex [" << v.get_idx() << "] contains only two or less incident edges" << endl;
}
else{
cout << "Boundary vertex [" << v.get_idx() << "] contains only two or less incident edges" << endl;
}
}
assert(link.size() >= 2);
}
// verify components of faces
for(FaceHandle f = faces_begin(); f != faces_end(); ++f){
// test faces for valid geometrical properties
if(no_edges(f) < 3){
cout << "Face [" << f.get_idx() << "] contains only " << no_edges(f) << " edge(s)" << endl;
}
// test that all halfedges in faces bind properly to their face
for(FaceCirculator fc(f); !fc.end(); ++fc){
if(fc.next_halfedge().face().get_idx() != f.get_idx()){
cout << "Face [" << f.get_idx() << "] is inconsistent" << endl;
return false;
}
// test for infinite loop around face
if(fc.no_steps() > halfedges.size() * 0.5f){
cout << "Face [" << f.get_idx() << "] loop contains more halfedges than manifold" << endl;
return false;
}
}
}
return true;
}
void Manifold::bbox(Vec3f& pmin, Vec3f& pmax)
{
VertexHandle v = vertices_begin();
pmin = pmax = v.get().pos;
++v;
for(; v != vertices_end(); ++v){
pmin = v_min(v.get().pos, pmin);
pmax = v_max(v.get().pos, pmax);
}
}
void Manifold::bsphere(Vec3f& c, float& r)
{
Vec3f p0, p7;
bbox(p0, p7);
Vec3f rad = (p7 - p0) * 0.5f;
c = p0 + rad;
r = rad.length();
}
void Manifold::remove_face_if_degenerate(HalfEdgeHandle h)
{
// face is degenerate if there is only two halfedges in face loop
if(h.next().next() == h)
{
HalfEdgeHandle hn = h.next();
HalfEdgeHandle ho = h.opp();
HalfEdgeHandle hno = hn.opp();
// glue opposite halfedge to halfedge opposite next halfedge, obsoleting h and hn from loop
glue(ho, hno);
VertexHandle hv = h.vert();
VertexHandle hnv = hn.vert();
// make v and vn point to opposite and next opposite halfedge, obsoleting h and hn from loop
hnv.get().out_idx = hno.get_idx();
hv.get().out_idx = ho.get_idx();
FaceHandle f = h.face();
// if face owning h is valid, remove face
if(f != NULL_FACE_HANDLE){
remove_face(f);
}
// remove the two invalid halfedges and the invalid face loop
remove_halfedge(h);
remove_halfedge(hn);
// verify that v and vn is sane after removing the degenerate face
check_boundary_consistency(hv);
check_boundary_consistency(hnv);
}
}
bool Manifold::collapse_halfedge_test(HalfEdgeHandle h)
{
// get the two vertices of the edge being tested
const VertexHandle v0 = h.opp().vert();
const VertexHandle v1 = h.vert();
// get the one-ring of v0
vector<VIDX> link0;
for(VertexCirculator vc0(v0); !vc0.end(); ++vc0){
link0.push_back(vc0.vertex().get_idx());
}
assert(link0.size() > 1);
// get the one-ring of v1
vector<VIDX> link1;
for(VertexCirculator vc1(v1); !vc1.end(); ++vc1){
link1.push_back(vc1.vertex().get_idx());
}
assert(link1.size() > 1);
// sort the vertices of the two rings
sort(link0.begin(), link0.end());
sort(link1.begin(), link1.end());
// get the intersection of the shared vertices from both rings
vector<VIDX> lisect;
std::back_insert_iterator<vector<VIDX> > lii(lisect);
set_intersection(link0.begin(), link0.end(),
link1.begin(), link1.end(),
lii);
int k = 0;
// if the adjacent face is a triangle (see 2)
if(h.next().next().next() == h){
VertexHandle v = h.next().vert();
// valency test (see 5)
if(valency(v) < 4){
return false;
}
// remove the vertex shared by the two rings from the intersection
vector<VIDX>::iterator iter;
iter = find(lisect.begin(), lisect.end(), v.get_idx());
assert(iter != lisect.end());
lisect.erase(iter);
++k;
}
// if the adjacent face is a triangle (see 2)
if(h.opp().next().next().next() == h.opp()){
VertexHandle v = h.opp().next().vert();
// valency test (see 5)
if(valency(v) < 4){
return false;
}
// remove the vertex shared by the two rings from the intersection
vector<VIDX>::iterator iter;
iter = find(lisect.begin(), lisect.end(), v.get_idx());
assert(iter != lisect.end());
lisect.erase(iter);
++k;
}
// double edge test (see 3)
if(lisect.size() != 0){
return false;
}
// tetrahedon test (see 4)
if(k == 2 && (link0.size() + link1.size() == 6)){
return false;
}
// test that we do not merge holes (see 6)
if(is_boundary(v0) && is_boundary(v1) && h.face() != NULL_FACE_HANDLE && h.opp().face() != NULL_FACE_HANDLE){
return false;
}
return true;
}
void Manifold::collapse_halfedge(HalfEdgeHandle h, bool avg_vertices)
{
HalfEdgeHandle ho = h.opp();
VertexHandle hv = h.vert();
VertexHandle hov = ho.vert();
// average the vertex positions
hv.get().pos = avg_vertices * 0.5f * (hov.get().pos - hv.get().pos) + hv.get().pos;
HalfEdgeHandle hn = h.next();
HalfEdgeHandle hp = h.prev();
HalfEdgeHandle hon = ho.next();
HalfEdgeHandle hop = ho.prev();
// update all halfedges pointing to hov to point to hv, effectively removing hov from all loops
for(VertexCirculator vc(hov); !vc.end(); ++vc){
vc.opp_halfedge().get().vert_idx = hv.get_idx();
}
hv.get().out_idx = hn.get_idx();
// link hp and hn, effectively removing h from opposite loop
link(hp, hn);
// make face owning h own hn instead
if(h.face() != NULL_FACE_HANDLE){
h.face().get().last_idx = hn.get_idx();
}
// link hop and hon, effectively removing h from opposite face loop
link(hop, hon);
// make opposite face owning h own hon instead
if(ho.face() != NULL_FACE_HANDLE){
ho.face().get().last_idx = hon.get_idx();
}
// remove the obsolete entities
remove_vertex(hov);
remove_halfedge(h);
remove_halfedge(ho);
// verify that remaining faces haven't become degenerate because of collapse
remove_face_if_degenerate(hn);
remove_face_if_degenerate(hon);
// verify that v is sane after collapse
check_boundary_consistency(hv);
}
FaceHandle Manifold::split_face_by_edge(FaceHandle f, VertexHandle v0, VertexHandle v1)
{
// make sure this is not a triangle
assert(no_edges(f) > 3);
// make sure we are not trying to connect a vertex to itself
assert(v0 != v1);
// find the halfege emanating from v0 which belongs to face being split
VertexCirculator vc0(v0);
while(vc0.halfedge().face() != f && !vc0.end()){
++vc0;
}
assert(!vc0.end());
HalfEdgeHandle h0 = vc0.halfedge();
assert(h0.face() == f);
// the halfedge belonging to f, going out from v0, is denoted h. Move along h until we hit v1.
// now we have the halfedge which belongs to f and points to v1.
HalfEdgeHandle h = h0;
while(h.vert() != v1){
h = h.next();
}
// create a new halfedge ha which connects v1 and v0 closing the firt loop.
assert(h != h0);
HalfEdgeHandle h1 = h.next();
HalfEdgeHandle ha = add_halfedge(HalfEdge());
link(h, ha);
link(ha, h0);
ha.get().face_idx = f.get_idx();
ha.get().vert_idx = v0.get_idx();
f.get().last_idx = ha.get_idx();
// create a new face, f2, and set all halfedges in the remaining part of the polygon to point to this face.
h = h1;
FaceHandle f2 = add_face(Face());
while(h.vert() != v0){
h.get().face_idx = f2.get_idx();
h = h.next();
}
h.get().face_idx = f2.get_idx();
assert(h != h1);
// create a new halfedge hb to connect v0 and v1.
HalfEdgeHandle hb = add_halfedge(HalfEdge());
link(h, hb);
link(hb, h1);
hb.get().face_idx = f2.get_idx();
hb.get().vert_idx = v1.get_idx();
f2.get().last_idx = hb.get_idx();
// complete the operation by gluing the two new halfedges
glue(ha, hb);
// assert sanity of operation
assert(h1.prev().opp().next() == h0);
assert(h0.prev().opp().next() == h1);
assert(hb.next().face() == f2);
assert(hb.next().next().face() == f2);
assert(hb.face() == f2);
// return handle to newly created face
return f2;
}
VertexHandle Manifold::split_face_by_vertex(FaceHandle f)
{
Vec3f p(0.0f);
FaceCirculator fc(f);
while(!fc.end()){
p += fc.vertex().get().pos;
++fc;
}
p /= fc.no_steps();
VertexHandle v = add_vertex(Vertex(p));
vector<HalfEdgeHandle> eout;
vector<HalfEdgeHandle> ein;
HalfEdgeHandle last = f.last();
HalfEdgeHandle h = last;
do{
HalfEdgeHandle hn = h.next();
HalfEdgeHandle o = add_halfedge(HalfEdge());
HalfEdgeHandle i = add_halfedge(HalfEdge());
FaceHandle fn = add_face(Face());
i.get().face_idx = fn.get_idx();
i.get().vert_idx = v.get_idx();
o.get().face_idx = fn.get_idx();
o.get().vert_idx = h.opp().vert().get_idx();
h.get().face_idx = fn.get_idx();
fn.get().last_idx = o.get_idx();
link(i, o);
link(o, h);
link(h, i);
eout.push_back(o);
ein.push_back(i);
h = hn;
}
while(h != last);
size_t N = eout.size();
for(size_t i = 0; i < N; ++i){
glue(ein[i], eout[(i+1)%N]);
}
v.get().out_idx = eout[0].get_idx();
remove_face(f);
return v;
}
VertexHandle Manifold::split_edge(HalfEdgeHandle h)
{
HalfEdgeHandle ho = h.opp();
VertexHandle v = h.vert();
VertexHandle vo = ho.vert();
Vec3f np = 0.5f * (v.get().pos + vo.get().pos);
VertexHandle vn = add_vertex(Vertex(np));
vn.get().out_idx = h.get_idx();
HalfEdgeHandle hn = add_halfedge(HalfEdge());
HalfEdgeHandle hno = add_halfedge(HalfEdge());
vo.get().out_idx = hn.get_idx();
v.get().out_idx = ho.get_idx();
glue(hn, hno);
link(h.prev(), hn);
link(hn, h);
hn.get().vert_idx = vn.get_idx();
link(hno, ho.next());
link(ho, hno);
hno.get().vert_idx = vo.get_idx();
ho.get().vert_idx = vn.get_idx();
if(h.face() != NULL_FACE_HANDLE){
h.face().get().last_idx = hn.get_idx();
}
if(ho.face() != NULL_FACE_HANDLE){
ho.face().get().last_idx = ho.get_idx();
}
hn.get().face_idx = h.face().get_idx();
hno.get().face_idx = ho.face().get_idx();
check_boundary_consistency(vn);
check_boundary_consistency(v);
check_boundary_consistency(vo);
return vn;
}
bool Manifold::merge_faces(FaceHandle f, HalfEdgeHandle h)
{
assert(h.face() == f);
HalfEdgeHandle ho = h.opp();
FaceHandle fo = ho.face();
HalfEdgeHandle hn = h.next();
HalfEdgeHandle hon = ho.next();
if(hn.vert() == hon.vert()){
return false;
}
HalfEdgeHandle hp = h.prev();
HalfEdgeHandle hop = ho.prev();
VertexHandle v = h.vert();
VertexHandle vo = ho.vert();
link(hop, hn);
v.get().out_idx = hn.get_idx();
link(hp, hon);
vo.get().out_idx = hn.get_idx();
f.get().last_idx = hn.get_idx();
HalfEdgeHandle hx = hon;
assert(hx.face() == fo);
while(h.face() != f){
hx.get().face_idx = f.get_idx();
hx = hx.next();
}
check_boundary_consistency(v);
check_boundary_consistency(vo);
remove_halfedge(h);
remove_halfedge(ho);
remove_face(fo);
return true;
}
void Manifold::close_hole(HalfEdgeHandle h)
{
if(h.face() == NULL_FACE_HANDLE){
FaceHandle f = add_face(Face());
f.get().last_idx = h.get_idx();
do{
h.get().face_idx = f.get_idx();
h = h.next();
}
while(h.face() != f);
}
}
void Manifold::enumerate_vertices()
{
size_t k = 0;
for(VertexHandle v = vertices_begin(); v != vertices_end(); ++v, ++k){
v.get().touched = k;
}
}
void Manifold::enumerate_halfedges()
{
size_t k = 0;
for(HalfEdgeHandle h = halfedges_begin(); h != halfedges_end(); ++h, ++k){
h.get().touched = k;
}
}
void Manifold::enumerate_faces()
{
size_t k = 0;
for(FaceHandle f = faces_begin(); f != faces_end(); ++f, ++k){
f.get().touched = k;
}
}
void Manifold::clear()
{
vertices.clear();
halfedges.clear();
faces.clear();
}
void Manifold::cleanup()
{
//1. store new location of all entities in seperate arrays for future lookups
vector<VIDX> vert_idx(vertices.full_size());
vector<FIDX> face_idx(faces.full_size());
vector<HIDX> he_idx(halfedges.full_size());
size_t k = 0;
for(VertexHandle v = vertices_begin(); v != vertices_end(); ++v, ++k){
vert_idx[v.get_idx()] = k;
}
k = 0;
for(FaceHandle f = faces_begin(); f != faces_end(); ++f, ++k){
face_idx[f.get_idx()] = k;
}
k = 0;
for(HalfEdgeHandle h = halfedges_begin(); h != halfedges_end(); ++h, ++k){
he_idx[h.get_idx()] = k;
}
//2. update the remainding attributes of all entities with the indices stored in the touched attributes of their known entities
for(VertexHandle v = vertices_begin(); v != vertices_end(); ++v){
v.get().out_idx = he_idx[v.out().get_idx()];
}
for(FaceHandle f = faces_begin(); f != faces_end(); ++f){
f.get().last_idx = he_idx[f.last().get_idx()];
}
for(HalfEdgeHandle h = halfedges_begin(); h != halfedges_end(); ++h){
if(h.face().get_idx() != NULL_FACE_IDX)
h.get().face_idx = face_idx[h.face().get_idx()];
h.get().next_idx = he_idx[h.next().get_idx()];
h.get().prev_idx = he_idx[h.prev().get_idx()];
h.get().opp_idx = he_idx[h.opp().get_idx()];
h.get().vert_idx = vert_idx[h.vert().get_idx()];
}
vertices.cleanup();
halfedges.cleanup();
faces.cleanup();
}
VertexHandle Manifold::vertex_handle(VIDX idx)
{
return VertexHandle(*this, idx);
}
HalfEdgeHandle Manifold::halfedge_handle(HIDX idx)
{
return HalfEdgeHandle(*this, idx);
}
FaceHandle Manifold::face_handle(FIDX idx)
{
return FaceHandle(*this, idx);
}
VertexHandle Manifold::vertices_begin()
{
size_t vertices_full_size = vertices.full_size();
VIDX idx = 0;
while(idx < vertices_full_size && !vertices.in_use(idx))
++idx;
return VertexHandle(*this, idx);
}
HalfEdgeHandle Manifold::halfedges_begin()
{
size_t halfedges_full_size = halfedges.full_size();
HIDX idx = 0;
while(idx < halfedges_full_size && !halfedges.in_use(idx))
++idx;
return HalfEdgeHandle(*this, idx);
}
FaceHandle Manifold::faces_begin()
{
size_t faces_full_size = faces.full_size();
FIDX idx = 0;
while(idx < faces_full_size && !faces.in_use(idx))
++idx;
return FaceHandle(*this, idx);
}
VertexHandle Manifold::vertices_end()
{
return VertexHandle(*this, vertices.full_size());
}
HalfEdgeHandle Manifold::halfedges_end()
{
return HalfEdgeHandle(*this, halfedges.full_size());
}
FaceHandle Manifold::faces_end()
{
return FaceHandle(*this, faces.full_size());
}
void Manifold::remove_vertex(VertexHandle v)
{
vertices.remove(v.get_idx());
}
void Manifold::remove_halfedge(HalfEdgeHandle h)
{
halfedges.remove(h.get_idx());
}
void Manifold::remove_face(FaceHandle f)
{
faces.remove(f.get_idx());
}
VertexHandle Manifold::add_vertex(const Vertex& v)
{
vertices.add(v);
return VertexHandle(*this, vertices.full_size() - 1);
}
HalfEdgeHandle Manifold::add_halfedge(const HalfEdge& h)
{
halfedges.add(h);
return HalfEdgeHandle(*this, halfedges.full_size() - 1);
}
FaceHandle Manifold::add_face(const Face& f)
{
faces.add(f);
return FaceHandle(*this, faces.full_size() - 1);
}
template
void Manifold::build(unsigned int no_vertices,
const float* vertvec,
unsigned int no_faces,
const unsigned int* facevec,
const unsigned int* indices,
const int* touch);
template
void Manifold::build(unsigned long no_vertices,
const float* vertvec,
unsigned long no_faces,
const unsigned long* facevec,
const unsigned long* indices,
const int* touch);
template
void Manifold::build(int no_vertices,
const float* vertvec,
int no_faces,
const int* facevec,
const int* indices,
const int* touch);
template
void Manifold::build(unsigned int no_vertices,
const double* vertvec,
unsigned int no_faces,
const unsigned int* facevec,
const unsigned int* indices,
const int* touch);
template
void Manifold::build(unsigned long no_vertices,
const double* vertvec,
unsigned long no_faces,
const unsigned long* facevec,
const unsigned long* indices,
const int* touch);
template
void Manifold::build(int no_vertices,
const double* vertvec,
int no_faces,
const int* facevec,
const int* indices,
const int* touch);
}