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.
282 lines
10 KiB
282 lines
10 KiB
3 years ago
|
#define CGAL_USE_BASIC_VIEWER
|
||
|
|
||
|
#include <CGAL/Exact_predicates_exact_constructions_kernel.h>
|
||
|
//#include <CGAL/Gmpq.h>
|
||
|
//#include <CGAL/Lazy_exact_nt.h>
|
||
|
//#include <CGAL/Simple_cartesian.h>
|
||
|
|
||
|
#include <CGAL/Delaunay_triangulation_2.h>
|
||
|
#include <CGAL/Triangulation_vertex_base_with_info_2.h>
|
||
|
#include <CGAL/Delaunay_triangulation_adaptation_traits_2.h>
|
||
|
#include <CGAL/Delaunay_triangulation_adaptation_policies_2.h>
|
||
|
|
||
|
#include <CGAL/Voronoi_diagram_2.h>
|
||
|
#include <CGAL/Boolean_set_operations_2.h>
|
||
|
#include <CGAL/bounding_box.h>
|
||
|
#include <CGAL/Polygon_2.h>
|
||
|
|
||
|
#include <iostream>
|
||
|
#include <cstdint>
|
||
|
#include <fstream>
|
||
|
#include <iostream>
|
||
|
|
||
|
#include "mex.hpp"
|
||
|
#include "mexAdapter.hpp"
|
||
|
#include "MatlabDataArray.hpp"
|
||
|
#include <omp.h>
|
||
|
|
||
|
//typedef CGAL::Lazy_exact_nt<CGAL::Gmpq> FT;
|
||
|
//typedef CGAL::Simple_cartesian<FT> K;
|
||
|
typedef CGAL::Exact_predicates_exact_constructions_kernel K;
|
||
|
|
||
|
typedef K::Iso_rectangle_2 Iso_rectangle2;
|
||
|
typedef K::Segment_2 Segment2;
|
||
|
typedef K::Ray_2 Ray2;
|
||
|
typedef K::Line_2 Line2;
|
||
|
typedef K::Point_2 Point2;
|
||
|
typedef CGAL::Polygon_2<K> Polygon2;
|
||
|
typedef Polygon2::Edge_const_iterator EdgeIterator;
|
||
|
|
||
|
typedef CGAL::Triangulation_vertex_base_with_info_2<unsigned int, K> Vb1;
|
||
|
typedef CGAL::Triangulation_data_structure_2<Vb1> Tds1;
|
||
|
typedef CGAL::Delaunay_triangulation_2<K, Tds1> DT;
|
||
|
|
||
|
typedef CGAL::Delaunay_triangulation_adaptation_traits_2<DT> AT;
|
||
|
typedef CGAL::Delaunay_triangulation_caching_degeneracy_removal_policy_2<DT> AP;
|
||
|
typedef CGAL::Voronoi_diagram_2<DT, AT, AP> VD;
|
||
|
typedef VD::Locate_result Locate_result;
|
||
|
typedef VD::Face::Ccb_halfedge_circulator Ccb_halfedge_circulator;
|
||
|
|
||
|
//Used to convert otherwise infinite rays into looooong line segments
|
||
|
const int RAY_LENGTH = 100;
|
||
|
|
||
|
//CGAL often returns objects that are either segments or rays. This converts
|
||
|
//these objects into segments. If the object would have resolved into a ray,
|
||
|
//that ray is intersected with the bounding box defined above and returned as
|
||
|
//a segment.
|
||
|
const auto ConvertToSeg = [&](const CGAL::Object seg_obj, bool outgoing) -> K::Segment_2
|
||
|
{
|
||
|
//One of these will succeed and one will have a NULL pointer
|
||
|
const K::Segment_2 *dseg = CGAL::object_cast<K::Segment_2>(&seg_obj);
|
||
|
const K::Ray_2 *dray = CGAL::object_cast<K::Ray_2>(&seg_obj);
|
||
|
if (dseg) { //Okay, we have a segment
|
||
|
return *dseg;
|
||
|
}
|
||
|
else { //Must be a ray
|
||
|
const auto &source = dray->source();
|
||
|
const auto dsx = source.x();
|
||
|
const auto dsy = source.y();
|
||
|
const auto &dir = dray->direction();
|
||
|
const auto tpoint = K::Point_2(dsx + RAY_LENGTH * dir.dx(), dsy + RAY_LENGTH * dir.dy());
|
||
|
if (outgoing)
|
||
|
return K::Segment_2(
|
||
|
dray->source(),
|
||
|
tpoint
|
||
|
);
|
||
|
else
|
||
|
return K::Segment_2(
|
||
|
tpoint,
|
||
|
dray->source()
|
||
|
);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
class MexFunction : public matlab::mex::Function {
|
||
|
public:
|
||
|
void operator()(matlab::mex::ArgumentList outputs, matlab::mex::ArgumentList inputs) {
|
||
|
|
||
|
checkArguments(outputs, inputs);
|
||
|
|
||
|
matlab::data::TypedArray<double> pnts = std::move(inputs[0]);
|
||
|
matlab::data::TypedArray<int> idx = std::move(inputs[1]);
|
||
|
double nelx = inputs[2][0];
|
||
|
double nely = inputs[3][0];
|
||
|
|
||
|
std::vector<Segment2> edges = mexCVT(pnts, idx, nelx, nely);
|
||
|
|
||
|
matlab::data::ArrayFactory factory;
|
||
|
matlab::data::TypedArray<double> edges_mat = factory.createArray<double>({ edges.size(), 4 });
|
||
|
|
||
|
//#pragma omp parallel for
|
||
|
for (int i = 0; i < edges.size(); ++i)
|
||
|
{
|
||
|
edges_mat[i][0] = CGAL::to_double(edges[i].point(0).x());
|
||
|
edges_mat[i][1] = CGAL::to_double(edges[i].point(0).y());
|
||
|
edges_mat[i][2] = CGAL::to_double(edges[i].point(1).x());
|
||
|
edges_mat[i][3] = CGAL::to_double(edges[i].point(1).y());
|
||
|
}
|
||
|
|
||
|
outputs[0] = edges_mat;
|
||
|
//outputs[1] = nodes_cell;
|
||
|
//outputs[2] = seeds;
|
||
|
}
|
||
|
|
||
|
void checkArguments(matlab::mex::ArgumentList outputs, matlab::mex::ArgumentList inputs) {
|
||
|
std::shared_ptr<matlab::engine::MATLABEngine> matlabPtr = getEngine();
|
||
|
matlab::data::ArrayFactory factory;
|
||
|
|
||
|
if (inputs.size() != 4) {
|
||
|
matlabPtr->feval(u"error",
|
||
|
0, std::vector<matlab::data::Array>({ factory.createScalar("Four inputs required") }));
|
||
|
}
|
||
|
|
||
|
if (inputs[0].getDimensions()[1] != 2) {
|
||
|
matlabPtr->feval(u"error",
|
||
|
0, std::vector<matlab::data::Array>({ factory.createScalar("Seeds input 1 must be NX2 dimension") }));
|
||
|
}
|
||
|
|
||
|
if (inputs[0].getType() != matlab::data::ArrayType::DOUBLE ||
|
||
|
inputs[0].getType() == matlab::data::ArrayType::COMPLEX_DOUBLE) {
|
||
|
matlabPtr->feval(u"error",
|
||
|
0, std::vector<matlab::data::Array>({ factory.createScalar("Seeds Input 1 must be a noncomplex scalar double") }));
|
||
|
}
|
||
|
|
||
|
if (inputs[1].getType() != matlab::data::ArrayType::INT32) {
|
||
|
matlabPtr->feval(u"error",
|
||
|
0, std::vector<matlab::data::Array>({ factory.createScalar("Index Input 2 must be type INT32") }));
|
||
|
}
|
||
|
|
||
|
if (inputs[1].getDimensions()[1] != 1) {
|
||
|
matlabPtr->feval(u"error",
|
||
|
0, std::vector<matlab::data::Array>({ factory.createScalar("Index Input 2 must be NX1 dimension") }));
|
||
|
}
|
||
|
|
||
|
for (int j = 2; j<4; ++j)
|
||
|
{
|
||
|
if (inputs[j].getDimensions()[0] != 1 || inputs[j].getDimensions()[1] != 1) {
|
||
|
matlabPtr->feval(u"error", 0,
|
||
|
std::vector<matlab::data::Array>({ factory.createScalar("nelx/nely input 3/4 must be 1X1 dimension") }));
|
||
|
}
|
||
|
if (inputs[j].getType() != matlab::data::ArrayType::DOUBLE ||
|
||
|
inputs[j].getType() == matlab::data::ArrayType::COMPLEX_DOUBLE) {
|
||
|
matlabPtr->feval(u"error",
|
||
|
0, std::vector<matlab::data::Array>({ factory.createScalar("nelx/nely input 3/4 must be a noncomplex scalar double") }));
|
||
|
}
|
||
|
}
|
||
|
if (outputs.size() > 1) {
|
||
|
matlabPtr->feval(u"error", 0,
|
||
|
std::vector<matlab::data::Array>({ factory.createScalar("Only one output is returned") }));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
std::vector<Segment2> mexCVT(matlab::data::TypedArray<double> seeds,
|
||
|
matlab::data::TypedArray<int> idx, double nelx, double nely)
|
||
|
{
|
||
|
std::vector<Point2> points;
|
||
|
|
||
|
unsigned int pnts_num = seeds.getDimensions()[0];
|
||
|
|
||
|
//#pragma omp parallel for
|
||
|
for (int i = 0; i < pnts_num; ++i)
|
||
|
{
|
||
|
double x = seeds[i][0];
|
||
|
double y = seeds[i][1];
|
||
|
Point2 p(x, y);
|
||
|
//Point2 p(seeds[i][0], seeds[i][1]);
|
||
|
points.push_back(p);
|
||
|
}
|
||
|
DT dt = DT(points.begin(), points.end());
|
||
|
|
||
|
std::shared_ptr<matlab::engine::MATLABEngine> matlabPtr = getEngine();
|
||
|
matlab::data::ArrayFactory factory;
|
||
|
if (!dt.is_valid()) {
|
||
|
matlabPtr->feval(u"error", 0,
|
||
|
std::vector<matlab::data::Array>({ factory.createScalar("Delaunay triangulation is not valid!") }));
|
||
|
}
|
||
|
|
||
|
VD vd(dt);
|
||
|
|
||
|
/// step.2 generate the outer-bounding
|
||
|
// cut the ray-edge under the boundary of [-halfx,-halfy] & [halfx, halfy]
|
||
|
double halfx = nelx / 2.;
|
||
|
double halfy = nely / 2.;
|
||
|
|
||
|
//In order to crop the Voronoi diagram, we need to convert the bounding box into a polygon.
|
||
|
CGAL::Polygon_2<K> bpoly;
|
||
|
bpoly.push_back(Point2(-halfx, -halfy));
|
||
|
bpoly.push_back(Point2(halfx, -halfy));
|
||
|
bpoly.push_back(Point2(halfx, halfy));
|
||
|
bpoly.push_back(Point2(-halfx, halfy));
|
||
|
|
||
|
/// step.3 get voronoi
|
||
|
std::vector<Segment2> edges;
|
||
|
|
||
|
unsigned int nidx = idx.getDimensions()[0];
|
||
|
//#pragma omp parallel for
|
||
|
for (int j = 0; j < nidx; ++j)
|
||
|
{
|
||
|
int k = idx[j] - 1;
|
||
|
if (k < 0 || k > pnts_num) {
|
||
|
matlabPtr->feval(u"error", 0,
|
||
|
std::vector<matlab::data::Array>({ factory.createScalar("The input idx is out of boundary!") }));
|
||
|
}
|
||
|
Point2 p = points[k];
|
||
|
|
||
|
Locate_result lr = vd.locate(p); // the face of p located
|
||
|
VD::Face_handle* f = boost::get<VD::Face_handle>(&lr);
|
||
|
|
||
|
Ccb_halfedge_circulator ec_start = (*f)->ccb(); // traversing the halfedges on the boundary of f
|
||
|
Ccb_halfedge_circulator ec = ec_start;
|
||
|
|
||
|
CGAL::Polygon_2<K> pgon;
|
||
|
|
||
|
do {
|
||
|
//A half edge circulator representing a ray doesn't carry direction
|
||
|
//information. To get it, we take the dual of the dual of the half-edge.
|
||
|
//The dual of a half-edge circulator is the edge of a Delaunay triangle.
|
||
|
//The dual of the edge of Delaunay triangle is either a segment or a ray.
|
||
|
const CGAL::Object seg_dual = vd.dual().dual(ec->dual());
|
||
|
|
||
|
//Convert the segment/ray into a segment
|
||
|
const auto this_seg = ConvertToSeg(seg_dual, ec->has_target());
|
||
|
|
||
|
pgon.push_back(this_seg.source());
|
||
|
|
||
|
//If the segment has no target, it's a ray. This means that the next
|
||
|
//segment will also be a ray. We need to connect those two rays with a
|
||
|
//segment. The following accomplishes this.
|
||
|
if (!ec->has_target()) {
|
||
|
const CGAL::Object nseg_dual = vd.dual().dual(ec->next()->dual());
|
||
|
const auto next_seg = ConvertToSeg(nseg_dual, ec->next()->has_target());
|
||
|
pgon.push_back(next_seg.target());
|
||
|
}
|
||
|
} while (++ec != ec_start); //Loop until we get back to the beginning
|
||
|
|
||
|
if (CGAL::do_intersect(pgon, bpoly)) // in fact, not necessarily
|
||
|
{
|
||
|
//Perform the intersection. Since CGAL is very general, it believes the
|
||
|
//result might be multiple polygons with holes.
|
||
|
std::list<CGAL::Polygon_with_holes_2<K>> isect;
|
||
|
CGAL::intersection(pgon, bpoly, std::back_inserter(isect));
|
||
|
|
||
|
//But we know better. The intersection of a convex polygon and a box is
|
||
|
//always a single polygon without holes. Let's assert this.
|
||
|
if (isect.size() != 1) {
|
||
|
matlabPtr->feval(u"error", 0,
|
||
|
std::vector<matlab::data::Array>({ factory.createScalar("[" +
|
||
|
std::to_string(k+1) + "] interpolated area != 1") }));
|
||
|
}
|
||
|
|
||
|
//And recover the polygon of interest
|
||
|
auto &poly_w_holes = isect.front();
|
||
|
auto &poly_outer = poly_w_holes.outer_boundary();
|
||
|
|
||
|
//Print the polygon as a WKT polygon
|
||
|
//std::cout << fnum << ", "
|
||
|
// "\"POLYGON ((";
|
||
|
for (auto v = poly_outer.vertices_begin(); v != poly_outer.vertices_end(); v++)
|
||
|
{
|
||
|
auto v1 = v + 1;
|
||
|
if (v + 1 == poly_outer.vertices_end())
|
||
|
v1 = poly_outer.vertices_begin();
|
||
|
//Point2 p1(v->x(), v->y());
|
||
|
//Point2 p2((v1)->x(), (v1)->y());
|
||
|
edges.push_back(Segment2(*v, *v1));
|
||
|
}
|
||
|
//std::cout << v->x() << " " << v->y() << ", ";
|
||
|
//std::cout << poly_outer.vertices_begin()->x() << " " << poly_outer.vertices_begin()->y() << "))\"\n";
|
||
|
}
|
||
|
}
|
||
|
return edges;
|
||
|
}
|
||
|
};
|