UNPKG

holes-in

Version:

Generates a 3D mesh from a 2D outer path and 2D inner paths

165 lines (152 loc) 6.93 kB
"use strict"; var cdt2d = require("cdt2d"); var pathHelper = require("./path-helper"); var constants = require("./constants"); var cdt2dHelper = { computeTriangulation: function computeTriangulation(paths, options) { var callStack = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 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); var cdtPoints = cdt2dHelper.clipperTocdt2d(paths); var cdtEdges = cdt2dHelper.pathsToEdges(paths); if (!options) { options = { exterior: false, interior: true }; } var triangles = cdt2d(cdtPoints, cdtEdges, options); // removes degenerated triangles: triangles = triangles.filter(function (triangle) { var ba = { x: cdtPoints[triangle[1]][0] - cdtPoints[triangle[0]][0], y: cdtPoints[triangle[1]][1] - cdtPoints[triangle[0]][1] }; var bc = { x: cdtPoints[triangle[1]][0] - cdtPoints[triangle[2]][0], y: cdtPoints[triangle[1]][1] - cdtPoints[triangle[2]][1] }; var lba = Math.sqrt(ba.x * ba.x + ba.y * ba.y); var lbc = Math.sqrt(bc.x * bc.x + bc.y * bc.y); ba.x /= lba; ba.y /= lba; bc.x /= lbc; bc.y /= lbc; var area = 0.5 * (ba.y * bc.x) - ba.x * bc.y; return Math.abs(area) > 0.001; }); if (paths.length && !triangles.length) { var foundProblem = 0; // checks if a point from a path if at the same position than a point from annother path. for (var i = 0; i < paths.length - 1; i++) { var path0 = paths[i]; for (var j = i + 1; j < paths.length; j++) { var path1 = paths[j]; for (var k = 0; k < path0.length; k++) { var point0 = path0[k]; for (var l = 0; l < path1.length; l++) { var point1 = path1[l]; if (pathHelper.getDistance(point0, point1) > 1) continue; var 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 (var _i = 0; _i < paths.length; _i++) { var path = paths[_i]; for (var _j = 0; _j < path.length - 1; _j++) { var segmentA = path[_j]; var segmentB = path[_j + 1]; for (var _k = 0; _k < path.length; _k++) { if (_k == _j || _k == _j + 1) continue; var point = path[_k]; if (!cdt2dHelper.isPointOnSegment(point, segmentA, segmentB)) continue; // displace the point: var norm = pathHelper.getDistance(segmentA, segmentB); var _repulsion = { X: -(segmentB.Y - segmentA.Y) / norm, Y: -(segmentB.X - segmentA.X) / norm }; var 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: triangles }; }, pathsToEdges: function pathsToEdges(paths) { var res = []; var offset = 0; for (var i = 0; i < paths.length; i++) { res = res.concat(cdt2dHelper.pathToEdges(paths[i], offset)); offset += paths[i].length; } return res; }, pathToEdges: function pathToEdges(path, offset) { if (path.length === 0) return []; var res = []; for (var i = 0; i < path.length - 1; i++) { res.push([offset + i, offset + i + 1]); } res.push([offset + path.length - 1, offset]); return res; }, clipperTocdt2d: function clipperTocdt2d(points) { var res = []; for (var i = 0; i < points.length; i++) { for (var j = 0; j < points[i].length; j++) { res.push(cdt2dHelper.clipperPointTocdt2dPoint(points[i][j])); } } return res; }, clipperPointTocdt2dPoint: function 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: function isPointOnSegment(p, ss, se) { var threshold = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 10; // save the length and normalize v // const v = se.subtract(ss); var v = { X: se.X - ss.X, Y: se.Y - ss.Y }; var 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); var w = { X: p.X - ss.X, Y: p.Y - ss.Y }; var h = v.X * w.Y - v.Y * w.X; var dot = v.X + w.X + v.Y + w.Y; return Math.abs(h) < threshold && dot >= -threshold && dot <= l + threshold; } }; module.exports = cdt2dHelper;