You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1162 lines
43 KiB

/*
* SceneReconstruct.cpp
*
* Copyright (c) 2014-2015 SEACAVE
*
* Author(s):
*
* cDc <cdc.seacave@gmail.com>
*
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*
* Additional Terms:
*
* You are required to preserve legal notices and author attributions in
* that material or in the Appropriate Legal Notices displayed by works
* containing it.
*/
#include "Common.h"
#include "Scene.h"
// Delaunay: mesh reconstruction
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Delaunay_triangulation_3.h>
#include <CGAL/Triangulation_vertex_base_with_info_3.h>
#include <CGAL/Triangulation_cell_base_with_info_3.h>
#include <CGAL/Spatial_sort_traits_adapter_3.h>
#include <CGAL/AABB_tree.h>
#include <CGAL/AABB_traits.h>
#include <CGAL/AABB_triangle_primitive.h>
#include <CGAL/Polyhedron_3.h>
using namespace MVS;
// D E F I N E S ///////////////////////////////////////////////////
// uncomment to enable multi-threading based on OpenMP
#ifdef _USE_OPENMP
#define DELAUNAY_USE_OPENMP
#endif
// uncomment to enable reconstruction algorithm of weakly supported surfaces
#define DELAUNAY_WEAKSURF
// uncomment to use IBFS algorithm for max-flow
// (faster, but not clear license policy)
#define DELAUNAY_MAXFLOW_IBFS
// S T R U C T S ///////////////////////////////////////////////////
#ifdef DELAUNAY_MAXFLOW_IBFS
#include "../Math/IBFS/IBFS.h"
template <typename NType, typename VType>
class MaxFlow
{
public:
// Type-Definitions
typedef NType node_type;
typedef VType value_type;
typedef IBFS::IBFSGraph graph_type;
public:
MaxFlow(size_t numNodes) {
graph.initSize((int)numNodes, (int)numNodes*2);
}
inline void AddNode(node_type n, value_type source, value_type sink) {
ASSERT(ISFINITE(source) && source >= 0 && ISFINITE(sink) && sink >= 0);
graph.addNode((int)n, source, sink);
}
inline void AddEdge(node_type n1, node_type n2, value_type capacity, value_type reverseCapacity) {
ASSERT(ISFINITE(capacity) && capacity >= 0 && ISFINITE(reverseCapacity) && reverseCapacity >= 0);
graph.addEdge((int)n1, (int)n2, capacity, reverseCapacity);
}
value_type ComputeMaxFlow() {
graph.initGraph();
return graph.computeMaxFlow();
}
inline bool IsNodeOnSrcSide(node_type n) const {
return graph.isNodeOnSrcSide((int)n);
}
protected:
graph_type graph;
};
#else
#include <boost/graph/graph_traits.hpp>
#include <boost/graph/one_bit_color_map.hpp>
#include <boost/property_map/property_map.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/boykov_kolmogorov_max_flow.hpp>
template <typename NType, typename VType>
class MaxFlow
{
public:
// Type-Definitions
typedef NType node_type;
typedef VType value_type;
typedef boost::vecS out_edge_list_t;
typedef boost::vecS vertex_list_t;
typedef boost::adjacency_list_traits<out_edge_list_t, vertex_list_t, boost::directedS> graph_traits;
typedef typename graph_traits::edge_descriptor edge_descriptor;
typedef typename graph_traits::vertex_descriptor vertex_descriptor;
typedef typename graph_traits::vertices_size_type vertex_size_type;
struct Edge {
value_type capacity;
value_type residual;
edge_descriptor reverse;
};
typedef boost::adjacency_list<out_edge_list_t, vertex_list_t, boost::directedS, size_t, Edge> graph_type;
typedef typename boost::graph_traits<graph_type>::edge_iterator edge_iterator;
typedef typename boost::graph_traits<graph_type>::out_edge_iterator out_edge_iterator;
public:
MaxFlow(size_t numNodes) : graph(numNodes+2), S(node_type(numNodes)), T(node_type(numNodes+1)) {}
void AddNode(node_type n, value_type source, value_type sink) {
ASSERT(ISFINITE(source) && source >= 0 && ISFINITE(sink) && sink >= 0);
if (source > 0) {
edge_descriptor e(boost::add_edge(S, n, graph).first);
edge_descriptor er(boost::add_edge(n, S, graph).first);
graph[e].capacity = source;
graph[e].reverse = er;
graph[er].reverse = e;
}
if (sink > 0) {
edge_descriptor e(boost::add_edge(n, T, graph).first);
edge_descriptor er(boost::add_edge(T, n, graph).first);
graph[e].capacity = sink;
graph[e].reverse = er;
graph[er].reverse = e;
}
}
void AddEdge(node_type n1, node_type n2, value_type capacity, value_type reverseCapacity) {
ASSERT(ISFINITE(capacity) && capacity >= 0 && ISFINITE(reverseCapacity) && reverseCapacity >= 0);
edge_descriptor e(boost::add_edge(n1, n2, graph).first);
edge_descriptor er(boost::add_edge(n2, n1, graph).first);
graph[e].capacity = capacity;
graph[er].capacity = reverseCapacity;
graph[e].reverse = er;
graph[er].reverse = e;
}
value_type ComputeMaxFlow() {
vertex_size_type n_verts(boost::num_vertices(graph));
color.resize(n_verts);
std::vector<edge_descriptor> pred(n_verts);
std::vector<vertex_size_type> dist(n_verts);
return boost::boykov_kolmogorov_max_flow(graph,
boost::get(&Edge::capacity, graph),
boost::get(&Edge::residual, graph),
boost::get(&Edge::reverse, graph),
&pred[0],
&color[0],
&dist[0],
boost::get(boost::vertex_index, graph),
S, T
);
}
inline bool IsNodeOnSrcSide(node_type n) const {
return (color[n] != boost::white_color);
}
protected:
graph_type graph;
std::vector<boost::default_color_type> color;
const node_type S;
const node_type T;
};
#endif
/*----------------------------------------------------------------*/
// S T R U C T S ///////////////////////////////////////////////////
// construct the mesh out of the dense point cloud using Delaunay tetrahedralization & graph-cut method
// see "Exploiting Visibility Information in Surface Reconstruction to Preserve Weakly Supported Surfaces", Jancosek and Pajdla, 2015
namespace DELAUNAY {
typedef CGAL::Exact_predicates_inexact_constructions_kernel kernel_t;
typedef kernel_t::Point_3 point_t;
typedef kernel_t::Vector_3 vector_t;
typedef kernel_t::Direction_3 direction_t;
typedef kernel_t::Segment_3 segment_t;
typedef kernel_t::Plane_3 plane_t;
typedef kernel_t::Triangle_3 triangle_t;
typedef kernel_t::Ray_3 ray_t;
typedef uint32_t vert_size_t;
typedef uint32_t cell_size_t;
typedef float edge_cap_t;
#ifdef DELAUNAY_WEAKSURF
struct view_info_t;
#endif
struct vert_info_t {
typedef edge_cap_t Type;
struct view_t {
PointCloud::View idxView; // view index
Type weight; // point's weight
inline view_t() {}
inline view_t(PointCloud::View _idxView, Type _weight) : idxView(_idxView), weight(_weight) {}
inline bool operator <(const view_t& v) const { return idxView < v.idxView; }
inline operator PointCloud::View() const { return idxView; }
};
typedef SEACAVE::cList<view_t,const view_t&,0,4,uint32_t> view_vec_t;
view_vec_t views; // faces' weight from the cell outwards
#ifdef DELAUNAY_WEAKSURF
view_info_t* viewsInfo; // each view caches the two faces from the point towards the camera and the end (used only by the weakly supported surfaces)
inline vert_info_t() : viewsInfo(NULL) {}
~vert_info_t();
void AllocateInfo();
#else
inline vert_info_t() {}
#endif
void InsertViews(const PointCloud& pc, PointCloud::Index idxPoint) {
const PointCloud::ViewArr& _views = pc.pointViews[idxPoint];
ASSERT(!_views.IsEmpty());
const PointCloud::WeightArr* pweights(pc.pointWeights.IsEmpty() ? NULL : pc.pointWeights.Begin()+idxPoint);
ASSERT(pweights == NULL || _views.GetSize() == pweights->GetSize());
FOREACH(i, _views) {
const PointCloud::View viewID(_views[i]);
const PointCloud::Weight weight(pweights ? (*pweights)[i] : PointCloud::Weight(1));
// insert viewID in increasing order
const uint32_t idx(views.FindFirstEqlGreater(viewID));
if (idx < views.GetSize() && views[idx] == viewID) {
// the new view is already in the array
ASSERT(views.FindFirst(viewID) == idx);
// update point's weight
views[idx].weight += weight;
} else {
// the new view is not in the array,
// insert it
views.InsertAt(idx, view_t(viewID, weight));
ASSERT(views.IsSorted());
}
}
}
};
struct cell_info_t {
typedef edge_cap_t Type;
Type f[4]; // faces' weight from the cell outwards
Type s; // cell's weight towards s-source
Type t; // cell's weight towards t-sink
inline const Type* ptr() const { return f; }
inline Type* ptr() { return f; }
};
typedef CGAL::Triangulation_vertex_base_with_info_3<vert_info_t, kernel_t> vertex_base_t;
typedef CGAL::Triangulation_cell_base_with_info_3<cell_size_t, kernel_t> cell_base_t;
typedef CGAL::Triangulation_data_structure_3<vertex_base_t, cell_base_t> triangulation_data_structure_t;
typedef CGAL::Delaunay_triangulation_3<kernel_t, triangulation_data_structure_t, CGAL::Compact_location> delaunay_t;
typedef delaunay_t::Vertex_handle vertex_handle_t;
typedef delaunay_t::Cell_handle cell_handle_t;
typedef delaunay_t::Facet facet_t;
typedef delaunay_t::Edge edge_t;
#ifdef DELAUNAY_WEAKSURF
struct view_info_t {
cell_handle_t cell2Cam;
cell_handle_t cell2End;
};
vert_info_t::~vert_info_t() {
delete[] viewsInfo;
}
void vert_info_t::AllocateInfo() {
ASSERT(!views.IsEmpty());
viewsInfo = new view_info_t[views.GetSize()];
#ifndef _RELEASE
memset(viewsInfo, 0, sizeof(view_info_t)*views.GetSize());
#endif
}
#endif
struct camera_cell_t {
cell_handle_t cell; // cell containing the camera
std::vector<facet_t> facets; // all facets on the convex-hull in view of the camera (ordered by importance)
};
struct adjacent_vertex_back_inserter_t {
const delaunay_t& delaunay;
const point_t& p;
vertex_handle_t& v;
inline adjacent_vertex_back_inserter_t(const delaunay_t& _delaunay, const point_t& _p, vertex_handle_t& _v) : delaunay(_delaunay), p(_p), v(_v) {}
inline adjacent_vertex_back_inserter_t& operator*() { return *this; }
inline adjacent_vertex_back_inserter_t& operator++(int) { return *this; }
inline void operator=(const vertex_handle_t& w) {
ASSERT(!delaunay.is_infinite(v));
if (!delaunay.is_infinite(w) && delaunay.geom_traits().compare_distance_3_object()(p, w->point(), v->point()) == CGAL::SMALLER)
v = w;
}
};
typedef TPoint3<kernel_t::RT> DPoint3;
template <typename TYPE>
inline TPoint3<TYPE> CGAL2MVS(const point_t& p) {
return TPoint3<TYPE>((TYPE)p.x(), (TYPE)p.y(), (TYPE)p.z());
}
template <typename TYPE>
inline point_t MVS2CGAL(const TPoint3<TYPE>& p) {
return point_t((kernel_t::RT)p.x, (kernel_t::RT)p.y, (kernel_t::RT)p.z);
}
// Given a facet, compute the plane containing it
inline Plane getFacetPlane(const facet_t& facet)
{
const point_t& v0(facet.first->vertex((facet.second+1)%4)->point());
const point_t& v1(facet.first->vertex((facet.second+2)%4)->point());
const point_t& v2(facet.first->vertex((facet.second+3)%4)->point());
return Plane(CGAL2MVS<REAL>(v0), CGAL2MVS<REAL>(v1), CGAL2MVS<REAL>(v2));
}
// Check if a point (p) is coplanar with a triangle (a, b, c);
// return orientation type
#if _PLATFORM_X86 && defined(__GNUC__)
#pragma GCC push_options
#pragma GCC target ("no-fma")
#endif
static inline int orientation(const point_t& a, const point_t& b, const point_t& c, const point_t& p)
{
#if 0
return CGAL::orientation(a, b, c, p);
#else
// inexact_orientation
const double& px = a.x(); const double& py = a.y(); const double& pz = a.z();
const double pqx(b.x()-px); const double prx(c.x()-px); const double psx(p.x()-px);
const double pqy(b.y()-py); const double pry(c.y()-py); const double psy(p.y()-py);
#if 1
const double det((pqx*pry-prx*pqy)*(p.z()-pz) - (pqx*psy-psx*pqy)*(c.z()-pz) + (prx*psy-psx*pry)*(b.z()-pz));
const double eps(1e-12);
#else // very slow due to ABS()
const double pqz(b.z()-pz); const double prz(c.z()-pz); const double psz(p.z()-pz);
const double det(CGAL::determinant(
pqx, pqy, pqz,
prx, pry, prz,
psx, psy, psz));
const double max0(MAXF3(ABS(pqx), ABS(pqy), ABS(pqz)));
const double max1(MAXF3(ABS(prx), ABS(pry), ABS(prz)));
const double eps(5.1107127829973299e-15 * MAXF(max0, max1));
#endif
if (det > eps) return CGAL::POSITIVE;
if (det < -eps) return CGAL::NEGATIVE;
return CGAL::COPLANAR;
#endif
}
#if _PLATFORM_X86 && defined(__GNUC__)
#pragma GCC pop_options
#endif
// Check if a point (p) is inside a frustum
// given the four corners (a, b, c, d) and the origin (o) of the frustum
inline bool checkPointInside(const point_t& a, const point_t& b, const point_t& c, const point_t& d, const point_t& o, const point_t& p)
{
return (
orientation(o, a, b, p) == CGAL::POSITIVE &&
orientation(o, b, c, p) == CGAL::POSITIVE &&
orientation(o, c, d, p) == CGAL::POSITIVE &&
orientation(o, d, a, p) == CGAL::POSITIVE
);
}
// Given a cell and a camera inside it, if the cell is infinite,
// find all facets on the convex-hull and inside the camera frustum,
// else return all four cell's facets
template <int FacetOrientation>
void fetchCellFacets(const delaunay_t& Tr, const std::vector<facet_t>& hullFacets, const cell_handle_t& cell, const Image& imageData, std::vector<facet_t>& facets)
{
if (!Tr.is_infinite(cell)) {
// store all 4 facets of the cell
for (int i=0; i<4; ++i) {
const facet_t f(cell, i);
ASSERT(!Tr.is_infinite(f));
facets.push_back(f);
}
return;
}
// find all facets on the convex-hull in camera's view
// create the 4 frustum planes
ASSERT(facets.empty());
const TFrustum<REAL,4> frustum(imageData.camera.P, imageData.width, imageData.height, 0, 1);
// loop over all cells
const point_t ptOrigin(MVS2CGAL(imageData.camera.C));
for (const facet_t& face: hullFacets) {
// add face if visible
const triangle_t verts(Tr.triangle(face));
if (orientation(verts[0], verts[1], verts[2], ptOrigin) != FacetOrientation)
continue;
AABB3 ab(CGAL2MVS<REAL>(verts[0]));
for (int i=1; i<3; ++i)
ab.Insert(CGAL2MVS<REAL>(verts[i]));
if (frustum.Classify(ab) == CULLED)
continue;
facets.push_back(face);
}
}
// information about an intersection between a segment and a facet
struct intersection_t {
enum Type {FACET, EDGE, VERTEX};
cell_handle_t ncell; // cell neighbor to the last intersected facet
vertex_handle_t v1; // vertex for vertex intersection, 1st edge vertex for edge intersection
vertex_handle_t v2; // 2nd edge vertex for edge intersection
facet_t facet; // intersected facet
Type type; // type of intersection (inside facet, on edge, or vertex)
REAL dist; // distance from starting point (camera) to this facet
bool bigger; // are we advancing away or towards the starting point?
const Ray3 ray; // the ray from starting point into the direction of the end point (point -> camera/end-point)
inline intersection_t() {}
inline intersection_t(const Point3& pt, const Point3& dir) : dist(-FLT_MAX), bigger(true), ray(pt, dir) {}
};
// Check if a segment (p, q) is coplanar with edges of a triangle (a, b, c):
// coplanar [in,out] : pointer to the 3 int array of indices of the edges coplanar with pq
// return number of entries in coplanar
inline int checkEdges(const point_t& a, const point_t& b, const point_t& c, const point_t& p, const point_t& q, int coplanar[3])
{
int nCoplanar(0);
switch (orientation(p,q,a,b)) {
case CGAL::POSITIVE: return -1;
case CGAL::COPLANAR: coplanar[nCoplanar++] = 0;
}
switch (orientation(p,q,b,c)) {
case CGAL::POSITIVE: return -1;
case CGAL::COPLANAR: coplanar[nCoplanar++] = 1;
}
switch (orientation(p,q,c,a)) {
case CGAL::POSITIVE: return -1;
case CGAL::COPLANAR: coplanar[nCoplanar++] = 2;
}
return nCoplanar;
}
// Check intersection between a facet (f) and a segment (s)
// (derived from CGAL::do_intersect in CGAL/Triangle_3_Segment_3_do_intersect.h)
// coplanar [out] : pointer to the 3 int array of indices of the edges coplanar with (s)
// return -1 if there is no intersection or
// the number of edges coplanar with the segment (0 = intersection inside the triangle)
int intersect(const triangle_t& t, const segment_t& s, int coplanar[3])
{
const point_t& a = t.vertex(0);
const point_t& b = t.vertex(1);
const point_t& c = t.vertex(2);
const point_t& p = s.source();
const point_t& q = s.target();
switch (orientation(a,b,c,p)) {
case CGAL::POSITIVE:
switch (orientation(a,b,c,q)) {
case CGAL::POSITIVE:
// the segment lies in the positive open halfspaces defined by the
// triangle's supporting plane
return -1;
case CGAL::COPLANAR:
// q belongs to the triangle's supporting plane
// p sees the triangle in counterclockwise order
return checkEdges(a,b,c,p,q,coplanar);
case CGAL::NEGATIVE:
// p sees the triangle in counterclockwise order
return checkEdges(a,b,c,p,q,coplanar);
default:
break;
}
case CGAL::NEGATIVE:
switch (orientation(a,b,c,q)) {
case CGAL::POSITIVE:
// q sees the triangle in counterclockwise order
return checkEdges(a,b,c,q,p,coplanar);
case CGAL::COPLANAR:
// q belongs to the triangle's supporting plane
// p sees the triangle in clockwise order
return checkEdges(a,b,c,q,p,coplanar);
case CGAL::NEGATIVE:
// the segment lies in the negative open halfspaces defined by the
// triangle's supporting plane
return -1;
default:
break;
}
case CGAL::COPLANAR: // p belongs to the triangle's supporting plane
switch (orientation(a,b,c,q)) {
case CGAL::POSITIVE:
// q sees the triangle in counterclockwise order
return checkEdges(a,b,c,q,p,coplanar);
case CGAL::COPLANAR:
// the segment is coplanar with the triangle's supporting plane
// as we know that it is inside the tetrahedron it intersects the face
//coplanar[0] = coplanar[1] = coplanar[2] = 3;
return 3;
case CGAL::NEGATIVE:
// q sees the triangle in clockwise order
return checkEdges(a,b,c,p,q,coplanar);
default:
break;
}
}
ASSERT("should not happen" == NULL);
return -1;
}
// Find which facet is intersected by the segment (seg) and return next facets to check:
// in_facets [in] : vector of facets to check
// out_facets [out] : vector of facets to check at next step (can be in_facets)
// out_inter [out] : kind of intersection
// return false if no intersection found and the end of the segment was not reached
bool intersect(const delaunay_t& Tr, const segment_t& seg, const std::vector<facet_t>& in_facets, std::vector<facet_t>& out_facets, intersection_t& inter)
{
ASSERT(!in_facets.empty());
static const int facet_vertex_order[] = {2,1,3,2,2,3,0,2,0,3,1,0,0,1,2,0};
int coplanar[3];
const REAL prevDist(inter.dist);
for (const facet_t& in_facet: in_facets) {
ASSERT(!Tr.is_infinite(in_facet));
const int nb_coplanar(intersect(Tr.triangle(in_facet), seg, coplanar));
if (nb_coplanar >= 0) {
// skip this cell if the intersection is not in the desired direction
const REAL interDist(inter.ray.IntersectsDist(getFacetPlane(in_facet)));
if ((interDist > prevDist) != inter.bigger)
continue;
// vertices of facet i: j = 4 * i, vertices = facet_vertex_order[j,j+1,j+2] negative orientation
inter.facet = in_facet;
inter.dist = interDist;
switch (nb_coplanar) {
case 0: {
// face intersection
inter.type = intersection_t::FACET;
// now find next facets to be checked as
// the three faces in the neighbor cell different than the origin face
out_facets.clear();
const cell_handle_t nc(inter.facet.first->neighbor(inter.facet.second));
ASSERT(!Tr.is_infinite(nc));
for (int i=0; i<4; ++i)
if (nc->neighbor(i) != inter.facet.first)
out_facets.push_back(facet_t(nc, i));
return true; }
case 1: {
// coplanar with 1 edge = intersect edge
const int j(4 * inter.facet.second);
const int i1(j + coplanar[0]);
inter.type = intersection_t::EDGE;
inter.v1 = inter.facet.first->vertex(facet_vertex_order[i1+0]);
inter.v2 = inter.facet.first->vertex(facet_vertex_order[i1+1]);
// now find next facets to be checked as
// the two faces in this cell opposing this edge
out_facets.clear();
const edge_t out_edge(inter.facet.first, facet_vertex_order[i1+0], facet_vertex_order[i1+1]);
const typename delaunay_t::Cell_circulator efc(Tr.incident_cells(out_edge));
typename delaunay_t::Cell_circulator ifc(efc);
do {
const cell_handle_t c(ifc);
if (c == inter.facet.first) continue;
const facet_t f1(c, c->index(inter.v1));
if (!Tr.is_infinite(f1))
out_facets.push_back(f1);
const facet_t f2(c, c->index(inter.v2));
if (!Tr.is_infinite(f2))
out_facets.push_back(f2);
} while (++ifc != efc);
return true; }
case 2: {
// coplanar with 2 edges = hit a vertex
// find vertex index
const int j(4 * inter.facet.second);
const int i1(j + coplanar[0]);
const int i2(j + coplanar[1]);
int i;
if (facet_vertex_order[i1] == facet_vertex_order[i2] || facet_vertex_order[i1] == facet_vertex_order[i2+1]) {
i = facet_vertex_order[i1];
} else
if (facet_vertex_order[i1+1] == facet_vertex_order[i2] || facet_vertex_order[i1+1] == facet_vertex_order[i2+1]) {
i = facet_vertex_order[i1+1];
} else {
ASSERT("2 edges intersections without common vertex" == NULL);
}
inter.type = intersection_t::VERTEX;
inter.v1 = inter.facet.first->vertex(i);
ASSERT(!Tr.is_infinite(inter.v1));
if (inter.v1->point() == seg.target()) {
// target reached
out_facets.clear();
return false;
}
// now find next facets to be checked as
// the faces in the cells around opposing this common vertex
out_facets.clear();
struct cell_back_inserter_t {
const delaunay_t& Tr;
const vertex_handle_t v;
const cell_handle_t current_cell;
std::vector<facet_t>& out_facets;
inline cell_back_inserter_t(const delaunay_t& _Tr, const intersection_t& inter, std::vector<facet_t>& _out_facets)
: Tr(_Tr), v(inter.v1), current_cell(inter.facet.first), out_facets(_out_facets) {}
inline cell_back_inserter_t& operator*() { return *this; }
inline cell_back_inserter_t& operator++(int) { return *this; }
inline void operator=(cell_handle_t c) {
if (c == current_cell)
return;
const facet_t f(c, c->index(v));
if (Tr.is_infinite(f))
return;
out_facets.push_back(f);
}
};
Tr.finite_incident_cells(inter.v1, cell_back_inserter_t(Tr, inter, out_facets));
return true; }
}
// coplanar with 3 edges = tangent = impossible?
break;
}
}
// Bad end: no intersection found and we are not at the end of the segment (very rarely, but it happens)!
out_facets.clear();
return false;
}
// same as above, but simplified only to find face intersection (otherwise terminate);
// terminate if cell containing the segment endpoint is found or if an infinite cell is encountered
bool intersectFace(const delaunay_t& Tr, const segment_t& seg, const std::vector<facet_t>& in_facets, std::vector<facet_t>& out_facets, intersection_t& inter)
{
int coplanar[3];
for (std::vector<facet_t>::const_iterator it=in_facets.cbegin(); it!=in_facets.cend(); ++it) {
ASSERT(!Tr.is_infinite(*it));
if (intersect(Tr.triangle(*it), seg, coplanar) == 0) {
// face intersection
inter.facet = *it;
inter.type = intersection_t::FACET;
// now find next facets to be checked as
// the three faces in the neighbor cell different than the origin face
out_facets.clear();
inter.ncell = inter.facet.first->neighbor(inter.facet.second);
if (Tr.is_infinite(inter.ncell))
return false;
for (int i=0; i<4; ++i)
if (inter.ncell->neighbor(i) != inter.facet.first)
out_facets.push_back(facet_t(inter.ncell, i));
return true;
}
}
out_facets.clear();
return false;
}
// same as above, but starts from a known vertex and incident cell
inline bool intersectFace(const delaunay_t& Tr, const segment_t& seg, const vertex_handle_t& v, const cell_handle_t& cell, std::vector<facet_t>& out_facets, intersection_t& inter)
{
if (cell == cell_handle_t())
return false;
if (Tr.is_infinite(cell)) {
inter.ncell = inter.facet.first = cell;
return true;
}
std::vector<facet_t>& in_facets = out_facets;
ASSERT(in_facets.empty());
in_facets.push_back(facet_t(cell, cell->index(v)));
return intersectFace(Tr, seg, in_facets, out_facets, inter);
}
// Given a cell, compute the free-space support for it
edge_cap_t freeSpaceSupport(const delaunay_t& Tr, const std::vector<cell_info_t>& infoCells, const cell_handle_t& cell)
{
// sum up all 4 incoming weights
// (corresponding to the 4 facets of the neighbor cells)
edge_cap_t wf(0);
for (int i=0; i<4; ++i) {
const facet_t& mfacet(Tr.mirror_facet(facet_t(cell, i)));
wf += infoCells[mfacet.first->info()].f[mfacet.second];
}
return wf;
}
// Fetch the triangle formed by the facet vertices,
// making sure the facet orientation is kept (as in CGAL::Triangulation_3::triangle())
// return the vertex handles of the triangle
struct triangle_vhandles_t {
vertex_handle_t verts[3];
triangle_vhandles_t() {}
triangle_vhandles_t(vertex_handle_t _v0, vertex_handle_t _v1, vertex_handle_t _v2)
#ifdef _SUPPORT_CPP11
: verts{_v0,_v1,_v2} {}
#else
{ verts[0] = _v0; verts[1] = _v1; verts[2] = _v2; }
#endif
};
inline triangle_vhandles_t getTriangle(cell_handle_t cell, int i)
{
ASSERT(i >= 0 && i <= 3);
if ((i&1) == 0)
return triangle_vhandles_t(
cell->vertex((i+2)&3),
cell->vertex((i+1)&3),
cell->vertex((i+3)&3) );
return triangle_vhandles_t(
cell->vertex((i+1)&3),
cell->vertex((i+2)&3),
cell->vertex((i+3)&3) );
}
// Compute the angle between the plane containing the given facet and the cell's circumscribed sphere
// return cosines of the angle
float computePlaneSphereAngle(const delaunay_t& Tr, const facet_t& facet)
{
// compute facet normal
if (Tr.is_infinite(facet.first))
return 1.f;
const triangle_vhandles_t tri(getTriangle(facet.first, facet.second));
const Point3f v0(CGAL2MVS<float>(tri.verts[0]->point()));
const Point3f v1(CGAL2MVS<float>(tri.verts[1]->point()));
const Point3f v2(CGAL2MVS<float>(tri.verts[2]->point()));
const Point3f fn((v1-v0).cross(v2-v0));
const float fnLenSq(normSq(fn));
if (fnLenSq == 0.f)
return 0.5f;
// compute the co-tangent to the circumscribed sphere in one of the vertices
#if CGAL_VERSION_NR < 1041101000
const Point3f cc(CGAL2MVS<float>(facet.first->circumcenter(Tr.geom_traits())));
#else
struct Tools {
static point_t circumcenter(const delaunay_t& Tr, const facet_t& facet) {
return Tr.geom_traits().construct_circumcenter_3_object()(
facet.first->vertex(0)->point(),
facet.first->vertex(1)->point(),
facet.first->vertex(2)->point(),
facet.first->vertex(3)->point()
);
}
};
const Point3f cc(CGAL2MVS<float>(Tools::circumcenter(Tr, facet)));
#endif
const Point3f ct(cc-v0);
const float ctLenSq(normSq(ct));
if (ctLenSq == 0.f)
return 0.5f;
// compute the angle between the two vectors
return CLAMP((fn.dot(ct))/SQRT(fnLenSq*ctLenSq), -1.f, 1.f);
}
} // namespace DELAUNAY
// First, iteratively create a Delaunay triangulation of the existing point-cloud by inserting point by point,
// iif the point to be inserted is not closer than distInsert pixels in at least one of its views to
// the projection of any of already inserted points.
// Next, the score is computed for all the edges of the directed graph composed of points as vertices.
// Finally, graph-cut algorithm is used to split the tetrahedrons in inside and outside,
// and the surface is such extracted.
bool Scene::ReconstructMesh(float distInsert, bool bUseFreeSpaceSupport, bool bUseOnlyROI, unsigned nItersFixNonManifold,
float kSigma, float kQual, float kb,
float kf, float kRel, float kAbs, float kOutl,
float kInf
)
{
using namespace DELAUNAY;
ASSERT(!pointcloud.IsEmpty());
mesh.Release();
// create the Delaunay triangulation
delaunay_t delaunay;
std::vector<cell_info_t> infoCells;
std::vector<camera_cell_t> camCells;
std::vector<facet_t> hullFacets;
{
TD_TIMER_STARTD();
std::vector<point_t> vertices(pointcloud.points.GetSize());
std::vector<std::ptrdiff_t> indices(pointcloud.points.GetSize());
// fetch points
if (bUseOnlyROI && !IsBounded())
bUseOnlyROI = false;
FOREACH(i, pointcloud.points) {
const PointCloud::Point& X(pointcloud.points[i]);
if (bUseOnlyROI && !obb.Intersects(X))
continue;
vertices[i] = point_t(X.x, X.y, X.z);
indices[i] = i;
}
// sort vertices
typedef CGAL::Spatial_sort_traits_adapter_3<delaunay_t::Geom_traits, point_t*> Search_traits;
CGAL::spatial_sort(indices.begin(), indices.end(), Search_traits(&vertices[0], delaunay.geom_traits()));
// insert vertices
Util::Progress progress(_T("Points inserted"), indices.size());
const float distInsertSq(SQUARE(distInsert));
vertex_handle_t hint;
delaunay_t::Locate_type lt;
int li, lj;
std::for_each(indices.cbegin(), indices.cend(), [&](size_t idx) {
const point_t& p = vertices[idx];
const PointCloud::Point& point = pointcloud.points[idx];
const PointCloud::ViewArr& views = pointcloud.pointViews[idx];
ASSERT(!views.IsEmpty());
if (hint == vertex_handle_t()) {
// this is the first point,
// insert it
hint = delaunay.insert(p);
ASSERT(hint != vertex_handle_t());
} else
if (distInsert <= 0) {
// insert all points
hint = delaunay.insert(p, hint);
ASSERT(hint != vertex_handle_t());
} else {
// locate cell containing this point
const cell_handle_t c(delaunay.locate(p, lt, li, lj, hint->cell()));
if (lt == delaunay_t::VERTEX) {
// duplicate point, nothing to insert,
// just update its visibility info
hint = c->vertex(li);
ASSERT(hint != delaunay.infinite_vertex());
} else {
// locate the nearest vertex
vertex_handle_t nearest;
if (delaunay.dimension() < 3) {
// use a brute-force algorithm if dimension < 3
delaunay_t::Finite_vertices_iterator vit = delaunay.finite_vertices_begin();
nearest = vit;
++vit;
adjacent_vertex_back_inserter_t inserter(delaunay, p, nearest);
for (delaunay_t::Finite_vertices_iterator end = delaunay.finite_vertices_end(); vit != end; ++vit)
inserter = vit;
} else {
// - start with the closest vertex from the located cell
// - repeatedly take the nearest of its incident vertices if any
// - if not, we're done
ASSERT(c != cell_handle_t());
nearest = delaunay.nearest_vertex_in_cell(p, c);
while (true) {
const vertex_handle_t v(nearest);
delaunay.adjacent_vertices(nearest, adjacent_vertex_back_inserter_t(delaunay, p, nearest));
if (v == nearest)
break;
}
}
ASSERT(nearest == delaunay.nearest_vertex(p, hint->cell()));
hint = nearest;
// check if point is far enough to all existing points
FOREACHPTR(pViewID, views) {
const Image& imageData = images[*pViewID];
const Point3f pn(imageData.camera.ProjectPointP3(point));
const Point3f pe(imageData.camera.ProjectPointP3(CGAL2MVS<float>(nearest->point())));
if (!IsDepthSimilar(pn.z, pe.z) || normSq(Point2f(pn)-Point2f(pe)) > distInsertSq) {
// point far enough to an existing point,
// insert as a new point
hint = delaunay.insert(p, lt, c, li, lj);
ASSERT(hint != vertex_handle_t());
break;
}
}
}
}
// update point visibility info
hint->info().InsertViews(pointcloud, idx);
++progress;
});
progress.close();
pointcloud.Release();
// init cells weights and
// loop over all cells and store the finite facet of the infinite cells
const size_t numNodes(delaunay.number_of_cells());
infoCells.resize(numNodes);
memset(&infoCells[0], 0, sizeof(cell_info_t)*numNodes);
cell_size_t ciID(0);
for (delaunay_t::All_cells_iterator ci=delaunay.all_cells_begin(), eci=delaunay.all_cells_end(); ci!=eci; ++ci, ++ciID) {
ci->info() = ciID;
// skip the finite cells
if (!delaunay.is_infinite(ci))
continue;
// find the finite face
for (int f=0; f<4; ++f) {
const facet_t facet(ci, f);
if (!delaunay.is_infinite(facet)) {
// store face
hullFacets.push_back(facet);
break;
}
}
}
// find all cells containing a camera
camCells.resize(images.GetSize());
FOREACH(i, images) {
const Image& imageData = images[i];
if (!imageData.IsValid())
continue;
const Camera& camera = imageData.camera;
camera_cell_t& camCell = camCells[i];
camCell.cell = delaunay.locate(MVS2CGAL(camera.C));
ASSERT(camCell.cell != cell_handle_t());
fetchCellFacets<CGAL::POSITIVE>(delaunay, hullFacets, camCell.cell, imageData, camCell.facets);
// link all cells contained by the camera to the source
for (const facet_t& f: camCell.facets)
infoCells[f.first->info()].s = kInf;
}
DEBUG_EXTRA("Delaunay tetrahedralization completed: %u points -> %u vertices, %u (+%u) cells, %u (+%u) faces (%s)", indices.size(), delaunay.number_of_vertices(), delaunay.number_of_finite_cells(), delaunay.number_of_cells()-delaunay.number_of_finite_cells(), delaunay.number_of_finite_facets(), delaunay.number_of_facets()-delaunay.number_of_finite_facets(), TD_TIMER_GET_FMT().c_str());
}
// for every camera-point ray intersect it with the tetrahedrons and
// add alpha_vis(point) to cell's directed edge in the graph
{
TD_TIMER_STARTD();
// estimate the size of the smallest reconstructible object
FloatArr distsSq(0, delaunay.number_of_edges());
for (delaunay_t::Finite_edges_iterator ei=delaunay.finite_edges_begin(), eei=delaunay.finite_edges_end(); ei!=eei; ++ei) {
const cell_handle_t& c(ei->first);
distsSq.Insert(normSq(CGAL2MVS<float>(c->vertex(ei->second)->point()) - CGAL2MVS<float>(c->vertex(ei->third)->point())));
}
const float sigma(SQRT(distsSq.GetMedian())*kSigma);
const float inv2SigmaSq(0.5f/(sigma*sigma));
distsSq.Release();
std::vector<facet_t> facets;
// compute the weights for each edge
{
TD_TIMER_STARTD();
Util::Progress progress(_T("Points weighted"), delaunay.number_of_vertices());
#ifdef DELAUNAY_USE_OPENMP
delaunay_t::Vertex_iterator vertexIter(delaunay.vertices_begin());
const int64_t nVerts(delaunay.number_of_vertices()+1);
#pragma omp parallel for private(facets)
for (int64_t i=0; i<nVerts; ++i) {
delaunay_t::Vertex_iterator vi;
#pragma omp critical
vi = vertexIter++;
#else
for (delaunay_t::Vertex_iterator vi=delaunay.vertices_begin(), vie=delaunay.vertices_end(); vi!=vie; ++vi) {
#endif
vert_info_t& vert(vi->info());
if (vert.views.IsEmpty())
continue;
#ifdef DELAUNAY_WEAKSURF
vert.AllocateInfo();
#endif
const point_t& p(vi->point());
const Point3 pt(CGAL2MVS<REAL>(p));
FOREACH(v, vert.views) {
const typename vert_info_t::view_t view(vert.views[v]);
const uint32_t imageID(view.idxView);
const edge_cap_t alpha_vis(view.weight);
const Image& imageData = images[imageID];
ASSERT(imageData.IsValid());
const Camera& camera = imageData.camera;
const camera_cell_t& camCell = camCells[imageID];
// compute the ray used to find point intersection
const Point3 vecCamPoint(pt-camera.C);
const REAL invLenCamPoint(REAL(1)/norm(vecCamPoint));
intersection_t inter(pt, Point3(vecCamPoint*invLenCamPoint));
// find faces intersected by the camera-point segment
const segment_t segCamPoint(MVS2CGAL(camera.C), p);
if (!intersect(delaunay, segCamPoint, camCell.facets, facets, inter))
continue;
do {
// assign score, weighted by the distance from the point to the intersection
const edge_cap_t w(alpha_vis*(1.f-EXP(-SQUARE((float)inter.dist)*inv2SigmaSq)));
edge_cap_t& f(infoCells[inter.facet.first->info()].f[inter.facet.second]);
#ifdef DELAUNAY_USE_OPENMP
#pragma omp atomic
#endif
f += w;
} while (intersect(delaunay, segCamPoint, facets, facets, inter));
ASSERT(facets.empty() && inter.type == intersection_t::VERTEX && inter.v1 == vi);
#ifdef DELAUNAY_WEAKSURF
ASSERT(vert.viewsInfo[v].cell2Cam == NULL);
vert.viewsInfo[v].cell2Cam = inter.facet.first;
#endif
// find faces intersected by the endpoint-point segment
inter.dist = FLT_MAX; inter.bigger = false;
const Point3 endPoint(pt+vecCamPoint*(invLenCamPoint*sigma));
const segment_t segEndPoint(MVS2CGAL(endPoint), p);
const cell_handle_t endCell(delaunay.locate(segEndPoint.source(), vi->cell()));
ASSERT(endCell != cell_handle_t());
fetchCellFacets<CGAL::NEGATIVE>(delaunay, hullFacets, endCell, imageData, facets);
edge_cap_t& t(infoCells[endCell->info()].t);
#ifdef DELAUNAY_USE_OPENMP
#pragma omp atomic
#endif
t += alpha_vis;
while (intersect(delaunay, segEndPoint, facets, facets, inter)) {
// assign score, weighted by the distance from the point to the intersection
const facet_t& mf(delaunay.mirror_facet(inter.facet));
const edge_cap_t w(alpha_vis*(1.f-EXP(-SQUARE((float)inter.dist)*inv2SigmaSq)));
edge_cap_t& f(infoCells[mf.first->info()].f[mf.second]);
#ifdef DELAUNAY_USE_OPENMP
#pragma omp atomic
#endif
f += w;
}
ASSERT(facets.empty() && inter.type == intersection_t::VERTEX && inter.v1 == vi);
#ifdef DELAUNAY_WEAKSURF
ASSERT(vert.viewsInfo[v].cell2End == NULL);
vert.viewsInfo[v].cell2End = inter.facet.first;
#endif
}
++progress;
}
progress.close();
DEBUG_ULTIMATE("\tweighting completed in %s", TD_TIMER_GET_FMT().c_str());
}
camCells.clear();
#ifdef DELAUNAY_WEAKSURF
// enforce t-edges for each point-camera pair with free-space support weights
if (bUseFreeSpaceSupport) {
TD_TIMER_STARTD();
#ifdef DELAUNAY_USE_OPENMP
delaunay_t::Vertex_iterator vertexIter(delaunay.vertices_begin());
const int64_t nVerts(delaunay.number_of_vertices()+1);
#pragma omp parallel for private(facets)
for (int64_t i=0; i<nVerts; ++i) {
delaunay_t::Vertex_iterator vi;
#pragma omp critical
vi = vertexIter++;
#else
for (delaunay_t::Vertex_iterator vi=delaunay.vertices_begin(), vie=delaunay.vertices_end(); vi!=vie; ++vi) {
#endif
const vert_info_t& vert(vi->info());
if (vert.views.IsEmpty())
continue;
const point_t& p(vi->point());
const Point3f pt(CGAL2MVS<float>(p));
FOREACH(v, vert.views) {
const uint32_t imageID(vert.views[(vert_info_t::view_vec_t::IDX)v]);
const Image& imageData = images[imageID];
ASSERT(imageData.IsValid());
const Camera& camera = imageData.camera;
// compute the ray used to find point intersection
const Point3f vecCamPoint(pt-Cast<float>(camera.C));
const float invLenCamPoint(1.f/norm(vecCamPoint));
// find faces intersected by the point-camera segment and keep the max free-space support score
const Point3f bgnPoint(pt-vecCamPoint*(invLenCamPoint*sigma*kf));
const segment_t segPointBgn(p, MVS2CGAL(bgnPoint));
intersection_t inter;
if (!intersectFace(delaunay, segPointBgn, vi, vert.viewsInfo[v].cell2Cam, facets, inter))
continue;
edge_cap_t beta(0);
do {
const edge_cap_t fs(freeSpaceSupport(delaunay, infoCells, inter.facet.first));
if (beta < fs)
beta = fs;
} while (intersectFace(delaunay, segPointBgn, facets, facets, inter));
// find faces intersected by the point-endpoint segment
const Point3f endPoint(pt+vecCamPoint*(invLenCamPoint*sigma*kb));
const segment_t segPointEnd(p, MVS2CGAL(endPoint));
if (!intersectFace(delaunay, segPointEnd, vi, vert.viewsInfo[v].cell2End, facets, inter))
continue;
edge_cap_t gammaMin(FLT_MAX), gammaMax(0);
do {
const edge_cap_t fs(freeSpaceSupport(delaunay, infoCells, inter.facet.first));
if (gammaMin > fs)
gammaMin = fs;
if (gammaMax < fs)
gammaMax = fs;
} while (intersectFace(delaunay, segPointEnd, facets, facets, inter));
const edge_cap_t gamma((gammaMin+gammaMax)*0.5f);
// if the point can be considered an interface point,
// enforce the t-edge weight of the end cell
const edge_cap_t epsAbs(beta-gamma);
const edge_cap_t epsRel(gamma/beta);
if (epsRel < kRel && epsAbs > kAbs && gamma < kOutl) {
edge_cap_t& t(infoCells[inter.ncell->info()].t);
#ifdef DELAUNAY_USE_OPENMP
#pragma omp atomic
#endif
t *= epsAbs;
}
}
}
DEBUG_ULTIMATE("\tt-edge reinforcement completed in %s", TD_TIMER_GET_FMT().c_str());
}
#endif
DEBUG_EXTRA("Delaunay tetrahedras weighting completed: %u cells, %u faces (%s)", delaunay.number_of_cells(), delaunay.number_of_facets(), TD_TIMER_GET_FMT().c_str());
}
// run graph-cut and extract the mesh
{
TD_TIMER_STARTD();
// create graph
MaxFlow<cell_size_t,edge_cap_t> graph(delaunay.number_of_cells());
// set weights
constexpr edge_cap_t maxCap(3.402823466e+34f/*FLT_MAX*0.0001f*/);
for (delaunay_t::All_cells_iterator ci=delaunay.all_cells_begin(), ce=delaunay.all_cells_end(); ci!=ce; ++ci) {
const cell_size_t ciID(ci->info());
const cell_info_t& ciInfo(infoCells[ciID]);
graph.AddNode(ciID, ciInfo.s, MINF(ciInfo.t, maxCap));
for (int i=0; i<4; ++i) {
const cell_handle_t cj(ci->neighbor(i));
const cell_size_t cjID(cj->info());
if (cjID < ciID) continue;
const cell_info_t& cjInfo(infoCells[cjID]);
const int j(cj->index(ci));
const edge_cap_t q((1.f - MINF(computePlaneSphereAngle(delaunay, facet_t(ci,i)), computePlaneSphereAngle(delaunay, facet_t(cj,j))))*kQual);
graph.AddEdge(ciID, cjID, ciInfo.f[i]+q, cjInfo.f[j]+q);
}
}
infoCells.clear();
// find graph-cut solution
const float maxflow(graph.ComputeMaxFlow());
// extract surface formed by the facets between inside/outside cells
const size_t nEstimatedNumVerts(delaunay.number_of_vertices());
std::unordered_map<void*,Mesh::VIndex> mapVertices;
#if defined(_MSC_VER) && (_MSC_VER > 1600)
mapVertices.reserve(nEstimatedNumVerts);
#endif
mesh.vertices.Reserve((Mesh::VIndex)nEstimatedNumVerts);
mesh.faces.Reserve((Mesh::FIndex)nEstimatedNumVerts*2);
for (delaunay_t::All_cells_iterator ci=delaunay.all_cells_begin(), ce=delaunay.all_cells_end(); ci!=ce; ++ci) {
const cell_size_t ciID(ci->info());
for (int i=0; i<4; ++i) {
if (delaunay.is_infinite(ci, i)) continue;
const cell_handle_t cj(ci->neighbor(i));
const cell_size_t cjID(cj->info());
if (ciID < cjID) continue;
const bool ciType(graph.IsNodeOnSrcSide(ciID));
if (ciType == graph.IsNodeOnSrcSide(cjID)) continue;
Mesh::Face& face = mesh.faces.AddEmpty();
const triangle_vhandles_t tri(getTriangle(ci, i));
for (int v=0; v<3; ++v) {
const vertex_handle_t vh(tri.verts[v]);
ASSERT(vh->point() == delaunay.triangle(ci,i)[v]);
const auto pairItID(mapVertices.insert(std::make_pair(vh.for_compact_container(), (Mesh::VIndex)mesh.vertices.GetSize())));
if (pairItID.second)
mesh.vertices.Insert(CGAL2MVS<Mesh::Vertex::Type>(vh->point()));
ASSERT(pairItID.first->second < mesh.vertices.GetSize());
face[v] = pairItID.first->second;
}
// correct face orientation
if (!ciType)
std::swap(face[0], face[2]);
}
}
delaunay.clear();
DEBUG_EXTRA("Delaunay tetrahedras graph-cut completed (%g flow): %u vertices, %u faces (%s)", maxflow, mesh.vertices.GetSize(), mesh.faces.GetSize(), TD_TIMER_GET_FMT().c_str());
}
// fix non-manifold vertices and edges
mesh.FixNonManifold();
return true;
}
/*----------------------------------------------------------------*/