holes-in
Version:
Generates a 3D mesh from a 2D outer path and 2D inner paths
169 lines (150 loc) • 6.51 kB
JavaScript
;
const cdt2d = require("cdt2d");
const pathHelper = require("./path-helper")
const constants = require("./constants");
const cdt2dHelper = {
computeTriangulation(paths, options, callStack = 0) {
if(callStack > 3){
console.warn("holes-in: warning a set points does not result in any triangulation.", JSON.stringify(paths));
return {triangles: [], faces: []};
}
// clean paths to avoid colinear and degenerated cases
paths = pathHelper.cleanPaths(paths);
const cdtPoints = cdt2dHelper.clipperTocdt2d(paths);
const cdtEdges = cdt2dHelper.pathsToEdges(paths);
if (!options) {
options = {
exterior: false,
interior: true,
};
}
let triangles = cdt2d(cdtPoints, cdtEdges, options);
// removes degenerated triangles:
triangles = triangles.filter( triangle => {
const ba = {x: cdtPoints[triangle[1]][0] - cdtPoints[triangle[0]][0],
y: cdtPoints[triangle[1]][1] - cdtPoints[triangle[0]][1]};
const bc = {x: cdtPoints[triangle[1]][0] - cdtPoints[triangle[2]][0],
y: cdtPoints[triangle[1]][1] - cdtPoints[triangle[2]][1]};
const lba = Math.sqrt(ba.x * ba.x + ba.y * ba.y);
const lbc = Math.sqrt(bc.x * bc.x + bc.y * bc.y);
ba.x /= lba;
ba.y /= lba;
bc.x /= lbc;
bc.y /= lbc;
const area = 0.5 * (ba.y * bc.x) - (ba.x * bc.y);
return Math.abs(area) > 0.001;
});
if(paths.length && !triangles.length) {
let foundProblem = 0;
// checks if a point from a path if at the same position than a point from annother path.
for(let i = 0; i< paths.length-1; i++){
const path0 = paths[i];
for(let j = i+1; j< paths.length; j++) {
const path1 = paths[j];
for(let k = 0 ; k< path0.length; k++){
const point0 = path0[k];
for(let l = 0 ; l < path1.length; l++) {
const point1 = path1[l];
if(pathHelper.getDistance(point0,point1) > 1) continue;
const repulsion = {X: Math.max(10, (point1.X - point0.X)/2), Y: Math.max(10, (point1.Y - point0.Y)/2,10) };
path0[k] = {X: point0.X + repulsion.X , Y: point0.Y + repulsion.Y }
path1[l] = {X: point1.X - repulsion.X , Y: point1.Y - repulsion.Y }
foundProblem++;
}
}
}
}
if(foundProblem){
return cdt2dHelper.computeTriangulation(paths, options, callStack+1);
}
// checks if a point if upon a segment
for(let i = 0; i< paths.length; i++){
const path = paths[i];
for(let j = 0 ; j< path.length-1; j++){
const segmentA = path[j];
const segmentB = path[j+1];
for(let k = 0; k < path.length; k++){
if(k == j || k == j+1) continue;
const point = path[k];
if(!cdt2dHelper.isPointOnSegment(point, segmentA, segmentB)) continue;
// displace the point:
const norm = pathHelper.getDistance(segmentA,segmentB);
const repulsion = {X: -(segmentB.Y - segmentA.Y)/norm, Y: -(segmentB.X - segmentA.X)/norm };
const vecPath = {X: path[(k+1)%path.length].X - point.X, Y: path[(k+1)%path.length].Y - point.Y}
if( vecPath.X * repulsion.X + vecPath.Y * repulsion.Y < 0) {
repulsion.X = - repulsion.X;
repulsion.Y = - repulsion.Y;
}
path[k] = {X: point.X + repulsion.X, Y: point.Y+repulsion.Y}
foundProblem++;
}
}
}
if(foundProblem){
return cdt2dHelper.computeTriangulation(paths, options, callStack+1);
}
}
return {
points: cdtPoints,
triangles,
};
},
pathsToEdges(paths) {
let res = [];
let offset = 0;
for (let i = 0; i < paths.length; i++) {
res = res.concat(cdt2dHelper.pathToEdges(paths[i], offset));
offset += paths[i].length;
}
return res;
},
pathToEdges(path, offset) {
if (path.length === 0) return [];
const res = [];
for (let i = 0; i < path.length - 1; i++) {
res.push([offset + i, offset + i + 1]);
}
res.push([offset + path.length - 1, offset]);
return res;
},
clipperTocdt2d(points) {
const res = [];
for (let i = 0; i < points.length; i++) {
for (let j = 0; j < points[i].length; j++) {
res.push(cdt2dHelper.clipperPointTocdt2dPoint(points[i][j]));
}
}
return res;
},
clipperPointTocdt2dPoint(point) {
return [point.X, point.Y];
},
/**
* Check if the point belongs to the segment.
* More presicely, if it's contained by the rectangle
* which contains the segment along its main axis
* and is small along the secondary axis
*
* @param {BABYLON.Vector2} p The point
* @param {BABYLON.Vector2} ss One extremum of the segment
* @param {BABYLON.Vector2} se The other extremum of the segment
* @param {Number} [threshold=helpers.math2d.DEFAULT_THRESHOLD]
* @return {Boolean}
*/
isPointOnSegment(p, ss, se, threshold = 10) {
// save the length and normalize v
// const v = se.subtract(ss);
const v = {X: se.X - ss.X, Y: se.Y - ss.Y};
const l = Math.sqrt(v.X* v.X + v.Y* v.Y);
v.X = v.X/ l;
v.Y = v.Y/ l;
// the minimal distance from the segment to the point
// and the projection of the point on the segment
// const w = p.subtract(ss);
const w = {X: p.X - ss.X, Y: p.Y - ss.Y};
const h = v.X * w.Y - v.Y * w.X;
const dot = v.X + w.X + v.Y + w.Y;
return (Math.abs(h) < threshold && dot >= -threshold && dot <= l + threshold);
},
};
module.exports = cdt2dHelper;