@kitware/vtk.js
Version:
Visualization Toolkit for the Web
1,658 lines (1,544 loc) • 56 kB
JavaScript
import { m as macro } from '../../../macros2.js';
import vtkPoints from '../../../Common/Core/Points.js';
import { s as subtract, j as cross, d as dot, n as norm, e as distance2BetweenPoints, k as add, l as normalize } from '../../../Common/Core/Math/index.js';
import vtkLine from '../../../Common/DataModel/Line.js';
import vtkPolygon from '../../../Common/DataModel/Polygon.js';
import vtkIncrementalOctreePointLocator from '../../../Common/DataModel/IncrementalOctreePointLocator.js';
import { VtkDataTypes } from '../../../Common/Core/DataArray/Constants.js';
import { CCS_POLYGON_TOLERANCE } from './Constants.js';
import { PolygonWithPointIntersectionState } from '../../../Common/DataModel/Polygon/Constants.js';
const {
vtkErrorMacro
} = macro;
/**
* Reverse the elements between the indices firstIdx and lastIdx of the given array arr.
*
* @param {Array|TypedArray} arr
* @param {Number} firstIdx
* @param {Number} lastIdx
*/
function reverseElements(arr) {
let firstIdx = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : undefined;
let lastIdx = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : undefined;
const first = firstIdx ?? 0;
const last = lastIdx ?? arr.length - 1;
const mid = first + Math.floor((last - first) / 2);
for (let i = first; i <= mid; ++i) {
[arr[i], arr[last - (i - first)]] = [arr[last - (i - first)], arr[i]];
}
}
// ---------------------------------------------------
/**
* Compute the quality of a triangle.
*
* @param {Vector3} p0
* @param {Vector3} p1
* @param {Vector3} p2
* @param {Vector3} normal
* @returns {Number}
*/
function vtkCCSTriangleQuality(p0, p1, p2, normal) {
const u = [];
const v = [];
const w = [];
subtract(p1, p0, u);
subtract(p2, p1, v);
subtract(p0, p2, w);
const area2 = (u[1] * v[2] - u[2] * v[1]) * normal[0] + (u[2] * v[0] - u[0] * v[2]) * normal[1] + (u[0] * v[1] - u[1] * v[0]) * normal[2];
let perim = Math.sqrt(u[0] * u[0] + u[1] * u[1] + u[2] * u[2]) + Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]) + Math.sqrt(w[0] * w[0] + w[1] * w[1] + w[2] * w[2]);
perim *= perim; // square the perimeter
perim = perim !== 0 ? perim : 1.0;
// use a normalization factor so equilateral quality is 1.0
return area2 / perim * 10.392304845413264;
}
// ---------------------------------------------------
/**
* Insert a triangle, and subdivide that triangle if one of
* its edges originally had more than two points before
* vtkCCSFindTrueEdges was called. Is called by vtkCCSTriangulate.
*
* @param {vtkCellArray} polys
* @param {Array|TypedArray} poly
* @param {Vector3} trids
* @param {Array|TypedArray} polyEdges
* @param {Array|TypedArray} originalEdges
*/
function vtkCCSInsertTriangle(polys, poly, trids, polyEdges, originalEdges) {
const nextVert = [1, 2, 0];
// To store how many of originalEdges match
let edgeCount = 0;
const edgeLocs = [-1, -1, -1];
// Check for original edge matches
for (let vert = 0; vert < 3; vert++) {
const currId = trids[vert];
const edgeLoc = polyEdges[currId];
if (edgeLoc >= 0) {
let nextId = currId + 1;
if (nextId === poly.length) {
nextId = 0;
}
// Is the triangle edge a polygon edge?
if (nextId === trids[nextVert[vert]]) {
edgeLocs[vert] = edgeLoc;
edgeCount++;
}
}
}
if (edgeCount === 0) {
// No special edge handling, so just do one triangle
polys.insertNextCell([poly[trids[0]], poly[trids[1]], poly[trids[2]]]);
} else {
// Make triangle fans for edges with extra points
const edgePts = [[poly[trids[0]], poly[trids[1]]], [poly[trids[1]], poly[trids[2]]], [poly[trids[2]], poly[trids[0]]]];
// Find out which edge has the most extra points
let maxPoints = 0;
let currSide = 0;
for (let i = 0; i < 3; i++) {
if (edgeLocs[i] >= 0) {
const edgeLoc = edgeLocs[i];
const npts = originalEdges[edgeLoc];
const pts = originalEdges.slice(edgeLoc + 1, edgeLoc + 1 + npts);
if (!(edgePts[i][0] === pts[0] || edgePts[i][1] === pts[npts - 1])) {
vtkErrorMacro('assertion error in vtkCCSInsertTriangle');
}
if (npts > maxPoints) {
maxPoints = npts;
currSide = i;
}
edgePts[i] = pts;
}
}
// Find the edges before/after the edge with most points
const prevSide = (currSide + 2) % 3;
const nextSide = (currSide + 1) % 3;
// If other edges have only 2 points, nothing to do with them
const prevNeeded = edgePts[prevSide].length > 2;
const nextNeeded = edgePts[nextSide].length > 2;
// The tail is the common point in the triangle fan
const tailPtIds = [];
tailPtIds[prevSide] = edgePts[currSide][1];
tailPtIds[currSide] = edgePts[prevSide][0];
tailPtIds[nextSide] = edgePts[currSide][edgePts[currSide].length - 2];
// Go through the sides and make the fans
for (let side = 0; side < 3; side++) {
if ((side !== prevSide || prevNeeded) && (side !== nextSide || nextNeeded)) {
let m = 0;
let n = edgePts[side].length - 1;
if (side === currSide) {
m += prevNeeded;
n -= nextNeeded;
}
for (let k = m; k < n; k++) {
polys.insertNextCell([edgePts[side][k], edgePts[side][k + 1], tailPtIds[side]]);
}
}
}
}
}
// ---------------------------------------------------
/**
* Triangulate a polygon that has been simplified by FindTrueEdges.
* This will re-insert the original edges. The output triangles are
* appended to "polys" and, for each stored triangle, "color" will
* be added to "scalars". The final two arguments (polygon and
* triangles) are only for temporary storage.
* The return value is true if triangulation was successful.
*
* @param {Array} poly
* @param {vtkPoints} points
* @param {Array} polyEdges
* @param {Array} originalEdges
* @param {vtkCellArray} polys
* @param {Vector3} normal
* @returns {boolean}
*/
function vtkCCSTriangulate(poly, points, polyEdges, originalEdges, polys, normal) {
let n = poly.length;
// If the poly is a line, then skip it
if (n < 3) {
return true;
}
// If the poly is a triangle, then pass it
if (n === 3) {
const trids = [0, 1, 2];
vtkCCSInsertTriangle(polys, poly, trids, polyEdges, originalEdges);
return true;
}
// If the poly has 4 or more points, triangulate it
let triangulationFailure = false;
let ppoint = [];
let point = [];
let npoint = [];
let i = 0;
let j = 0;
let k = 0;
const verts = [];
verts.length = n;
for (i = 0; i < n; i++) {
verts[i] = [i, 0];
}
// compute the triangle quality for each vert
k = n - 2;
points.getPoint(poly[verts[k][0]], point);
i = n - 1;
points.getPoint(poly[verts[i][0]], npoint);
let concave = 0;
let maxq = 0;
let maxi = 0;
for (j = 0; j < n; j++) {
[ppoint, point, npoint] = [point, npoint, ppoint];
points.getPoint(poly[verts[j][0]], npoint);
const q = vtkCCSTriangleQuality(ppoint, point, npoint, normal);
if (q > maxq) {
maxi = i;
maxq = q;
}
concave += q < 0;
verts[i][1] = q;
i = j;
}
let foundEar;
// perform the ear-cut triangulation
for (;;) {
// if no potential ears were found, then fail
if (maxq <= Number.MIN_VALUE) {
triangulationFailure = true;
break;
}
i = maxi;
j = i + 1 !== n ? i + 1 : 0;
k = i !== 0 ? i - 1 : n - 1;
if (verts[i][1] > 0) {
foundEar = true;
points.getPoint(poly[verts[j][0]], npoint);
points.getPoint(poly[verts[k][0]], ppoint);
// only do ear check if there are concave vertices
if (concave) {
// get the normal of the split plane
const v = [];
const u = [];
subtract(npoint, ppoint, v);
cross(v, normal, u);
const d = dot(ppoint, u);
let jj = j + 1 !== n ? j + 1 : 0;
let x = [];
points.getPoint(poly[verts[jj][0]], x);
let side = dot(x, u) < d;
let foundNegative = side;
// check for crossings of the split plane
jj = jj + 1 !== n ? jj + 1 : 0;
let y = [];
const s = [];
const t = [];
for (; foundEar && jj !== k; jj = jj + 1 !== n ? jj + 1 : 0) {
[x, y] = [y, x];
points.getPoint(poly[verts[jj][0]], x);
const sside = dot(x, u) < d;
// XOR
if (side ? !sside : sside) {
side = !side;
foundNegative = true;
foundEar = vtkLine.intersection(ppoint, npoint, x, y, s, t) === vtkLine.IntersectionState.NO_INTERSECTION;
}
}
foundEar &&= foundNegative;
}
if (!foundEar) {
// don't try again until it is split
verts[i][1] = Number.MIN_VALUE;
} else {
// create a triangle from vertex and neighbors
const trids = [verts[i][0], verts[j][0], verts[k][0]];
vtkCCSInsertTriangle(polys, poly, trids, polyEdges, originalEdges);
// remove the vertex i
verts.splice(i, 1);
k -= i === 0;
j -= j !== 0;
// break if this was final triangle
if (--n < 3) {
break;
}
// re-compute quality of previous point
const kk = k !== 0 ? k - 1 : n - 1;
points.getPoint(poly[verts[kk][0]], point);
const kq = vtkCCSTriangleQuality(point, ppoint, npoint, normal);
concave -= verts[k][1] < 0 && kq >= 0;
verts[k][1] = kq;
// re-compute quality of next point
const jj = j + 1 !== n ? j + 1 : 0;
points.getPoint(poly[verts[jj][0]], point);
const jq = vtkCCSTriangleQuality(ppoint, npoint, point, normal);
concave -= verts[j][1] < 0 && jq >= 0;
verts[j][1] = jq;
}
}
// find the highest-quality ear candidate
maxi = 0;
maxq = verts[0][1];
for (i = 1; i < n; i++) {
const q = verts[i][1];
if (q > maxq) {
maxi = i;
maxq = q;
}
}
}
return !triangulationFailure;
}
// ---------------------------------------------------
/**
* Create polygons from line segments.
*
* @param {vtkPolyData} polyData
* @param {Number} firstLine
* @param {Number} endLine
* @param {Boolean} oriented
* @param {Array} newPolys
* @param {Array} incompletePolys
*/
function vtkCCSMakePolysFromLines(polyData, firstLine, endLine, oriented, newPolys, incompletePolys) {
let npts = 0;
let pts = [];
// Bitfield for marking lines as used
const usedLines = new Uint8Array(endLine - firstLine); // defaults to 0
// Require cell links to get lines from pointIds
polyData.buildLinks(polyData.getPoints().getNumberOfPoints());
let numNewPolys = 0;
let remainingLines = endLine - firstLine;
while (remainingLines > 0) {
// Create a new poly
const polyId = numNewPolys++;
const poly = [];
newPolys.push(poly);
let lineId = 0;
let completePoly = false;
// start the poly
for (lineId = firstLine; lineId < endLine; lineId++) {
if (!usedLines[lineId - firstLine]) {
pts = polyData.getCellPoints(lineId).cellPointIds;
npts = pts.length;
let n = npts;
if (npts > 2 && pts[0] === pts[npts - 1]) {
n = npts - 1;
completePoly = true;
}
poly.length = n;
for (let i = 0; i < n; i++) {
poly[i] = pts[i];
}
break;
}
}
usedLines[lineId - firstLine] = 1;
remainingLines--;
let noLinesMatch = remainingLines === 0 && !completePoly;
while (!completePoly && !noLinesMatch && remainingLines > 0) {
// This is cleared if a match is found
noLinesMatch = true;
// Number of points in the poly
const npoly = poly.length;
const lineEndPts = [];
const endPts = [poly[npoly - 1], poly[0]];
// For both open ends of the polygon
for (let endIdx = 0; endIdx < 2; endIdx++) {
const matches = [];
const cells = polyData.getPointCells(endPts[endIdx]);
// Go through all lines that contain this endpoint
for (let icell = 0; icell < cells.length; icell++) {
lineId = cells[icell];
if (lineId >= firstLine && lineId < endLine && !usedLines[lineId - firstLine]) {
pts = polyData.getCellPoints(lineId).cellPointIds;
npts = pts.length;
lineEndPts[0] = pts[0];
lineEndPts[1] = pts[npts - 1];
// Check that poly end matches line end
if (endPts[endIdx] === lineEndPts[endIdx] || !oriented && endPts[endIdx] === lineEndPts[1 - endIdx]) {
matches.push(lineId);
}
}
}
if (matches.length > 0) {
// Multiple matches mean we need to decide which path to take
if (matches.length > 1) {
// Remove double-backs
let k = matches.length;
do {
lineId = matches[--k];
pts = polyData.getCellPoints(lineId).cellPointIds;
npts = pts.length;
lineEndPts[0] = pts[0];
lineEndPts[1] = pts[npts - 1];
// check if line is reversed
const r = endPts[endIdx] !== lineEndPts[endIdx];
if (!r && (endIdx === 0 && poly[npoly - 2] === pts[1] || endIdx === 1 && poly[1] === pts[npts - 2]) || r && (endIdx === 0 && poly[npoly - 2] === pts[npts - 2] || endIdx === 1 && poly[1] === pts[1])) {
matches.splice(k, 1);
}
} while (k > 0 && matches.length > 1);
// If there are multiple matches due to intersections,
// they should be dealt with here.
}
lineId = matches[0];
pts = polyData.getCellPoints(lineId).cellPointIds;
npts = pts.length;
lineEndPts[0] = pts[0];
lineEndPts[1] = pts[npts - 1];
// Do both ends match?
if (endPts[endIdx] === lineEndPts[endIdx]) {
completePoly = endPts[1 - endIdx] === lineEndPts[1 - endIdx];
} else {
completePoly = endPts[1 - endIdx] === lineEndPts[endIdx];
}
if (endIdx === 0) {
for (let i = 1; i < npts - (completePoly ? 1 : 0); i++) {
poly.push(pts[i]);
}
} else {
for (let i = completePoly ? 1 : 0; i < npts - 1; i++) {
poly.unshift(pts[i]);
}
}
if (endPts[endIdx] !== lineEndPts[endIdx]) {
// reverse the ids in the added line
let pit = poly.length;
let ptsIt = completePoly ? 1 : 0;
let ptsEnd = npts - 1;
if (endIdx === 1) {
pit = npts - 1 - (completePoly ? 1 : 0);
ptsIt = 1;
ptsEnd = npts - (completePoly ? 1 : 0);
}
while (ptsIt !== ptsEnd) {
poly[--pit] = poly[ptsIt++];
}
}
usedLines[lineId - firstLine] = 1;
remainingLines--;
noLinesMatch = false;
}
}
}
// Check for incomplete polygons
if (noLinesMatch) {
incompletePolys.push(polyId);
}
}
}
// ---------------------------------------------------
/**
* Join polys that have loose ends, as indicated by incompletePolys.
* Any polys created will have a normal opposite to the supplied normal,
* and any new edges that are created will be on the hull of the point set.
* Shorter edges will be preferred over long edges.
*
* @param {Array[]} polys
* @param {Array} incompletePolys
* @param {vtkPoints} points
* @param {Vector3} normal
*/
function vtkCCSJoinLooseEnds(polys, incompletePolys, points, normal) {
// Relative tolerance for checking whether an edge is on the hull
const tol = CCS_POLYGON_TOLERANCE;
// A list of polys to remove when everything is done
const removePolys = [];
const p1 = [];
const p2 = [];
let poly1;
let poly2;
let pt1;
let pt2;
let dMin;
let iMin;
let v;
let d;
let n = incompletePolys.length;
while (n !== 0) {
poly1 = polys[incompletePolys[n - 1]];
pt1 = poly1[poly1.length - 1];
points.getPoint(pt1, p1);
dMin = Number.MAX_VALUE;
iMin = 0;
for (let i = 0; i < n; i++) {
poly2 = polys[incompletePolys[i]];
pt2 = poly2[0];
points.getPoint(pt2, p2);
// The next few steps verify that edge [p1, p2] is on the hull
v = [p2[0] - p1[0], p2[1] - p1[1], p2[2] - p1[2]];
d = norm(v);
if (d !== 0) {
v[0] /= d;
v[1] /= d;
v[2] /= d;
}
// Compute the midpoint of the edge
const pm = [0.5 * (p1[0] + p2[0]), 0.5 * (p1[1] + p2[1]), 0.5 * (p1[2] + p2[2])];
// Create a plane equation
const pc = [];
cross(normal, v, pc);
pc[3] = -dot(pc, pm);
// Check that all points are inside the plane. If they aren't, then
// the edge is not on the hull of the pointset.
let badPoint = false;
const m = polys.length;
const p = [];
for (let j = 0; j < m && !badPoint; j++) {
const poly = polys[j];
const npts = poly.length;
for (let k = 0; k < npts; k++) {
const ptId = poly[k];
if (ptId !== pt1 && ptId !== pt2) {
points.getPoint(ptId, p);
const val = p[0] * pc[0] + p[1] * pc[1] + p[2] * pc[2] + pc[3];
const r2 = distance2BetweenPoints(p, pm);
// Check distance from plane against the tolerance
if (val < 0 && val * val > tol * tol * r2) {
badPoint = true;
break;
}
}
}
// If no bad points, then this edge is a candidate
if (!badPoint && d < dMin) {
dMin = d;
iMin = i;
}
}
}
// If a match was found, append the polys
if (dMin < Number.MAX_VALUE) {
// Did the poly match with itself?
if (iMin === n - 1) {
// Mark the poly as closed
incompletePolys.pop();
} else {
const id2 = incompletePolys[iMin];
// Combine the polys
// for (let i = 1; i < polys[id2].length; i++) {
// poly1.push(polys[id2][i]);
// }
poly1.push(...polys[id2]);
// Erase the second poly
removePolys.push(id2);
incompletePolys.splice(iMin, 1);
}
} else {
// If no match, erase this poly from consideration
removePolys.push(incompletePolys[n - 1]);
incompletePolys.pop();
}
n = incompletePolys.length;
}
// Remove polys that couldn't be completed
removePolys.sort((a, b) => a - b);
let i = removePolys.length;
while (i > 0) {
// Remove items in reverse order
polys.splice(removePolys[--i], 1);
}
// Clear the incompletePolys vector, it's indices are no longer valid
incompletePolys.length = 0;
}
// ---------------------------------------------------
/**
* Given three vectors p.p1, p.p2, and p.p3, this routine
* checks to see if progressing from p1 to p2 to p3 is a clockwise
* or counterclockwise progression with respect to the normal.
* The return value is -1 for clockwise, +1 for counterclockwise,
* and 0 if any two of the vectors are coincident.
*
* @param {Vector3} p
* @param {Vector3} p1
* @param {Vector3} p2
* @param {Vector3} p3
* @param {Vector3} normal
* @returns {Number}
*/
function vtkCCSVectorProgression(p, p1, p2, p3, normal) {
const v1 = [p1[0] - p[0], p1[1] - p[1], p1[2] - p[2]];
const v2 = [p2[0] - p[0], p2[1] - p[1], p2[2] - p[2]];
const v3 = [p3[0] - p[0], p3[1] - p[1], p3[2] - p[2]];
const w1 = [];
const w2 = [];
cross(v2, v1, w1);
cross(v2, v3, w2);
const s1 = dot(w1, normal);
const s2 = dot(w2, normal);
if (s1 !== 0 && s2 !== 0) {
const sb1 = s1 < 0;
const sb2 = s2 < 0;
// if sines have different signs
// XOR
if (sb1 ? !sb2 : sb2) {
// return -1 if s2 is -ve
return 1 - 2 * sb2;
}
const c1 = dot(v2, v1);
const l1 = norm(v1);
const c2 = dot(v2, v3);
const l2 = norm(v3);
// ck is the difference of the cosines, flipped in sign if sines are +ve
const ck = (c2 * l2 - c1 * l1) * (1 - sb1 * 2);
if (ck !== 0) {
// return the sign of ck
return 1 - 2 * (ck < 0);
}
}
return 0;
}
// ---------------------------------------------------
/**
* Check for self-intersection. Split the figure-eights.
* This assumes that all intersections occur at existing
* vertices, i.e. no new vertices will be created. Returns
* the number of splits made.
*
* @param {Array[]} polys
* @param {vtkPoints} points
* @param {Array} polyGroups
* @param {Array} polyEdges
* @param {Vector3} normal
* @param {Boolean} oriented
*/
function vtkCCSSplitAtPinchPoints(polys, points, polyGroups, polyEdges, normal, oriented) {
const tryPoints = vtkPoints.newInstance({
dataType: VtkDataTypes.DOUBLE,
empty: true
});
const locator = vtkIncrementalOctreePointLocator.newInstance();
let splitCount = 0;
let poly;
let n;
let bounds;
let tol;
for (let i = 0; i < polys.length; i++) {
poly = polys[i];
n = poly.length;
bounds = [];
tol = CCS_POLYGON_TOLERANCE * Math.sqrt(vtkPolygon.getBounds(poly, points, bounds));
if (tol === 0) {
// eslint-disable-next-line no-continue
continue;
}
tryPoints.initialize();
locator.setTolerance(tol);
locator.initPointInsertion(tryPoints, bounds);
let foundMatch = false;
let idx1 = 0;
let idx2 = 0;
let unique = 0;
const point = [];
const p1 = [];
const p2 = [];
const p3 = [];
for (idx2 = 0; idx2 < n; idx2++) {
points.getPoint(poly[idx2], point);
const {
success,
pointIdx
} = locator.insertUniquePoint(point, 0);
if (!success) {
// Need vertIdx to match poly indices, so force point insertion
locator.insertNextPoint(point);
// Do the points have different pointIds?
idx1 = pointIdx;
unique = poly[idx2] !== poly[idx1];
if (idx2 > idx1 + 2 - unique && n + idx1 > idx2 + 2 - unique) {
if (oriented) {
// Make sure that splitting this poly won't create a hole poly
let prevIdx = n + idx1 - 1;
let midIdx = idx1 + 1;
let nextIdx = idx2 + 1;
if (prevIdx >= n) {
prevIdx -= n;
}
if (midIdx >= n) {
midIdx -= n;
}
if (nextIdx >= n) {
nextIdx -= n;
}
points.getPoint(poly[prevIdx], p1);
points.getPoint(poly[midIdx], p2);
points.getPoint(poly[nextIdx], p3);
if (vtkCCSVectorProgression(point, p1, p2, p3, normal) > 0) {
foundMatch = true;
break;
}
} else {
foundMatch = true;
break;
}
}
}
}
if (foundMatch) {
splitCount++;
// Split off a new poly
const m = idx2 - idx1;
const oldPoly = polys[i];
const oldEdges = polyEdges[i];
const newPoly1 = oldPoly.slice(idx1, idx1 + m + unique);
const newEdges1 = oldEdges.slice(idx1, idx1 + m + unique);
const newPoly2 = new Array(n - m + unique);
const newEdges2 = new Array(n - m + unique);
if (unique) {
newEdges1[m] = -1;
}
// The poly that is split off, which might have more intersections
for (let j = 0; j < idx1 + unique; j++) {
newPoly2[j] = oldPoly[j];
newEdges2[j] = oldEdges[j];
}
if (unique) {
newEdges2[idx1] = -1;
}
for (let k = idx2; k < n; k++) {
newPoly2[k - m + unique] = oldPoly[k];
newEdges2[k - m + unique] = oldEdges[k];
}
polys[i] = newPoly1;
polyEdges[i] = newEdges1;
polys.push(newPoly2);
polyEdges.push(newEdges2);
// Unless polygroup was clear (because poly was reversed),
// make a group with one entry for the new poly
polyGroups.length = polys.length;
if (polyGroups[i].length > 0) {
polyGroups[polys.length - 1].push(polys.length - 1);
}
}
}
return splitCount;
}
// ---------------------------------------------------
/**
* The polygons might have a lot of extra points, i.e. points
* in the middle of the edges. Remove those points, but keep
* the original edges as polylines in the originalEdges array.
* Only original edges with more than two points will be kept.
*
* @param {Array[]} polys
* @param {vtkPoints} points
* @param {Array} polyEdges
* @param {Array} originalEdges
*/
function vtkCCSFindTrueEdges(polys, points, polyEdges, originalEdges) {
// Tolerance^2 for angle to see if line segments are parallel
const atol2 = CCS_POLYGON_TOLERANCE * CCS_POLYGON_TOLERANCE;
const p0 = [];
const p1 = [];
const p2 = [];
const v1 = [];
const v2 = [];
let l1;
let l2;
for (let polyId = 0; polyId < polys.length; polyId++) {
const oldPoly = polys[polyId];
const n = oldPoly.length;
const newEdges = [];
polyEdges.push(newEdges);
// Only useful if poly has more than three sides
if (n < 4) {
newEdges[0] = -1;
newEdges[1] = -1;
newEdges[2] = -1;
// eslint-disable-next-line no-continue
continue;
}
// While we remove points, m keeps track of how many points are left
let m = n;
// Compute bounds for tolerance
const bounds = [];
const tol2 = vtkPolygon.getBounds(oldPoly, points, bounds) * atol2;
// The new poly
const newPoly = [];
let cornerPointId = 0;
let oldOriginalId = -1;
// Keep the partial edge from before the first corner is found
const partialEdge = [];
let cellCount = 0;
points.getPoint(oldPoly[n - 1], p0);
points.getPoint(oldPoly[0], p1);
subtract(p1, p0, v1);
l1 = dot(v1, v1);
for (let j = 0; j < n; j++) {
let k = j + 1;
if (k >= n) {
k -= n;
}
points.getPoint(oldPoly[k], p2);
subtract(p2, p1, v2);
l2 = dot(v2, v2);
// Dot product is |v1||v2|cos(theta)
const c = dot(v1, v2);
// sin^2(theta) = (1 - cos^2(theta))
// and c*c = l1*l2*cos^2(theta)
const s2 = l1 * l2 - c * c;
// In the small angle approximation, sin(theta) == theta, so
// s2/(l1*l2) is the angle that we want to check, but it's not
// a valid check if l1 or l2 is very close to zero.
const pointId = oldPoly[j];
// Keep the point if:
// 1) removing it would create a 2-point poly OR
// 2) it's more than "tol" distance from the prev point AND
// 3) the angle is greater than atol:
if (m <= 3 || l1 > tol2 && (c < 0 || l1 < tol2 || l2 < tol2 || s2 > l1 * l2 * atol2)) {
// Complete the previous edge only if the final point count
// will be greater than two
if (cellCount > 1) {
if (pointId !== oldOriginalId) {
originalEdges.push(pointId);
cellCount++;
}
// Update the number of segments in the edge
const countLocation = originalEdges.length - cellCount - 1;
originalEdges[countLocation] = cellCount;
newEdges.push(countLocation);
} else if (cellCount === 0) {
partialEdge.push(pointId);
} else {
newEdges.push(-1);
}
newPoly.push(pointId);
// Start a new edge with cornerPointId as a "virtual" point
cornerPointId = pointId;
oldOriginalId = pointId;
cellCount = 1;
// Rotate to the next point
p0[0] = p2[0];
p0[1] = p2[1];
p0[2] = p2[2];
p1[0] = p2[0];
p1[1] = p2[1];
p1[2] = p2[2];
v1[0] = v2[0];
v1[1] = v2[1];
v1[2] = v2[2];
l1 = l2;
} else {
if (cellCount > 0 && pointId !== oldOriginalId) {
// First check to see if we have to add cornerPointId
if (cellCount === 1) {
originalEdges.push(1); // new edge
originalEdges.push(cornerPointId);
}
// Then add the new point
originalEdges.push(pointId);
oldOriginalId = pointId;
cellCount++;
} else {
// No corner yet, so save the point
partialEdge.push(pointId);
}
// Reduce the count
m--;
// Join the previous two segments, since the point was removed
p1[0] = p2[0];
p1[1] = p2[1];
p1[2] = p2[2];
subtract(p2, p0, v1);
l1 = dot(v1, v1);
}
}
// Add the partial edge to the end
for (let ii = 0; ii < partialEdge.length; ii++) {
const pointId = partialEdge[ii];
if (pointId !== oldOriginalId) {
if (cellCount === 1) {
originalEdges.push(1); // new edge
originalEdges.push(cornerPointId);
}
originalEdges.push(pointId);
oldOriginalId = pointId;
cellCount++;
}
}
// Finalize
if (cellCount > 1) {
// Update the number of segments in the edge
const countLocation = originalEdges.length - cellCount - 1;
originalEdges[countLocation] = cellCount;
newEdges.push(countLocation);
}
polys[polyId] = newPoly;
}
}
// ---------------------------------------------------
/**
* Reverse a cleaned-up polygon along with the info about
* all of its original vertices.
*
* @param {Array} poly
* @param {Array} edges
* @param {Array} originalEdges
*/
function vtkCCSReversePoly(poly, edges, originalEdges) {
reverseElements(poly, 1, poly.length - 1);
edges.reverse();
for (let i = 0; i < edges.length; i++) {
if (edges[i] >= 0) {
const firstPtsIdx = edges[i] + 1;
const npts = originalEdges[edges[i]];
reverseElements(originalEdges, firstPtsIdx, firstPtsIdx + npts - 1);
}
}
}
// ---------------------------------------------------
/**
* Check the sense of the polygon against the given normal. Returns
* zero if the normal is zero.
*
* @param {Array} poly
* @param {vtkPoints} points
* @param {Vector3} normal
*/
function vtkCCSCheckPolygonSense(poly, points, normal) {
// Compute the normal
const pnormal = [0.0, 0.0, 0.0];
const p0 = [];
const p1 = [];
const p2 = [];
const v1 = [];
const v2 = [];
const v = [];
points.getPoint(poly[0], p0);
points.getPoint(poly[1], p1);
subtract(p1, p0, v1);
for (let jj = 2; jj < poly.length; jj++) {
points.getPoint(poly[jj], p2);
subtract(p2, p0, v2);
cross(v1, v2, v);
add(pnormal, v, pnormal);
p1[0] = p2[0];
p1[1] = p2[1];
p1[2] = p2[2];
v1[0] = v2[0];
v1[1] = v2[1];
v1[2] = v2[2];
}
// Check the normal
const d = dot(pnormal, normal);
return {
isNormalNotZero: d !== 0,
sense: d > 0
};
}
// ---------------------------------------------------
/**
* Check whether innerPoly is inside outerPoly.
* The normal is needed to verify the polygon orientation.
* The values of pp, bounds, and tol2 must be precomputed
* by calling vtkCCSPrepareForPolyInPoly() on outerPoly.
*
* @param {Array} outerPoly
* @param {Array} innerPoly
* @param {vtkPoints} points
* @param {Vector3} normal
* @param {Float64Array} pp
* @param {Bounds} bounds
* @param {Number} tol2
*/
function vtkCCSPolyInPoly(outerPoly, innerPoly, points, normal, pp, bounds, tol2) {
// Find a vertex of poly "j" that isn't on the edge of poly "i".
// This is necessary or the PointInPolygon might return "true"
// based only on roundoff error.
const n = outerPoly.length;
const m = innerPoly.length;
const p = [];
const q1 = [];
const q2 = [];
for (let jj = 0; jj < m; jj++) {
// Semi-randomize the point order
// eslint-disable-next-line no-bitwise
const kk = (jj >> 1) + (jj & 1) * (m + 1 >> 1);
points.getPoint(innerPoly[kk], p);
const intersectionState = vtkPolygon.pointInPolygon(p, pp, bounds, normal);
if (intersectionState === PolygonWithPointIntersectionState.FAILURE) {
vtkErrorMacro('Error finding point in polygon in vtkCCSPolyInPoly');
}
if (intersectionState !== PolygonWithPointIntersectionState.OUTSIDE) {
let pointOnEdge = 0;
points.getPoint(outerPoly[n - 1], q1);
for (let ii = 0; ii < n; ii++) {
points.getPoint(outerPoly[ii], q2);
// This method returns distance squared
const {
distance
} = vtkLine.distanceToLine(p, q1, q2);
if (distance < tol2) {
pointOnEdge = 1;
break;
}
q1[0] = q2[0];
q1[1] = q2[1];
q1[2] = q2[2];
}
if (!pointOnEdge) {
// Good result, point is in polygon
return true;
}
}
}
// No matches found
return false;
}
// ---------------------------------------------------
/**
* Precompute values needed for the PolyInPoly check.
* The values that are returned are as follows:
* pp: an array of the polygon vertices
* bounds: the polygon bounds
* tol2: a tolerance value based on the size of the polygon
* (note: pp must be pre-allocated to the 3*outerPoly.length)
*
* @param {Array} outerPoly
* @param {vtkPoints} points
* @param {Float64Array} pp
* @param {Bounds} bounds
*/
function vtkCCSPrepareForPolyInPoly(outerPoly, points, pp, bounds) {
const n = outerPoly.length;
if (n === 0) {
return 0.0; // to avoid false positive warning about uninitialized value
}
// Pull out the points
const point = [];
let j = 0;
for (let i = 0; i < n; i++) {
points.getPoint(outerPoly[i], point);
pp[j++] = point[0];
pp[j++] = point[1];
pp[j++] = point[2];
}
// Find the bounding box and tolerance for the polygon
return vtkPolygon.getBounds(outerPoly, points, bounds) * (CCS_POLYGON_TOLERANCE * CCS_POLYGON_TOLERANCE);
}
// ---------------------------------------------------
/**
* Check for polygons within polygons. Group the polygons
* if they are within each other. Reverse the sense of
* the interior "hole" polygons. A hole within a hole
* will be reversed twice and will become its own group.
*
* @param {Array} newPolys
* @param {vtkPoints} points
* @param {Array} polyGroups
* @param {Array} polyEdges
* @param {Array} originalEdges
* @param {Vector3} normal
* @param {Boolean} oriented
*/
function vtkCCSMakeHoleyPolys(newPolys, points, polyGroups, polyEdges, originalEdges, normal, oriented) {
const numNewPolys = newPolys.length;
if (numNewPolys <= 1) {
return;
}
// Use bit arrays to keep track of inner polys
const polyReversed = [];
const innerPolys = [];
// GroupCount is an array only needed for unoriented polys
let groupCount;
if (!oriented) {
groupCount = new Int32Array(numNewPolys);
}
// Find the maximum poly size
let nmax = 1;
for (let kk = 0; kk < numNewPolys; kk++) {
nmax = Math.max(nmax, newPolys[kk].length);
}
// These are some values needed for poly-in-poly checks
const pp = new Float64Array(3 * nmax);
const bounds = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0];
let tol2;
// Go through all polys
for (let i = 0; i < numNewPolys; i++) {
const n = newPolys[i].length;
if (n < 3) {
// eslint-disable-next-line no-continue
continue;
}
// Check if poly is reversed
const {
isNormalNotZero,
sense
} = vtkCCSCheckPolygonSense(newPolys[i], points, normal);
if (isNormalNotZero) {
polyReversed[i] = !sense;
}
// Precompute some values needed for poly-in-poly checks
tol2 = vtkCCSPrepareForPolyInPoly(newPolys[i], points, pp, bounds);
// Look for polygons inside of this one
for (let j = 0; j < numNewPolys; j++) {
if (j !== i && newPolys[j].length >= 3) {
// Make sure polygon i is not in polygon j
const pg = polyGroups[j];
if (!pg.includes(i)) {
if (vtkCCSPolyInPoly(newPolys[i], newPolys[j], points, normal, pp.subarray(3 * n), bounds, tol2)) {
// Add to group
polyGroups[i].push(j);
if (groupCount) {
groupCount[j] += 1;
}
}
}
}
}
}
if (!oriented) {
// build a stack of polys that aren't inside other polys=
const outerPolyStack = [];
for (let ll = 0; ll < numNewPolys; ll++) {
if (groupCount[ll] === 0) {
outerPolyStack.push(ll);
}
}
let j;
while (outerPolyStack.length > 0) {
j = outerPolyStack.length - 1;
outerPolyStack.pop();
if (polyReversed[j]) {
vtkCCSReversePoly(newPolys[j], polyEdges[j], originalEdges);
polyReversed[j] = false;
}
if (polyGroups[j].length > 1) {
// Convert the group into a bit array, to make manipulation easier
innerPolys.length = 0;
for (let k = 1; k < polyGroups[j].length; k++) {
const jj = polyGroups[j][k];
if (groupCount[jj] > 1) {
groupCount[jj] -= 2;
if (groupCount[jj] === 0) {
outerPolyStack.push(jj);
}
} else {
innerPolys[jj] = 1;
polyGroups[jj].length = 0;
if (!polyReversed[jj]) {
vtkCCSReversePoly(newPolys[jj], polyEdges[jj], originalEdges);
polyReversed[jj] = false;
}
}
}
// Use the bit array to recreate the polyGroup
polyGroups[j].length = 0;
polyGroups[j].push(j);
for (let jj = 0; jj < numNewPolys; jj++) {
if (innerPolys[jj]) {
polyGroups[j].push(jj);
}
}
}
}
} else {
// oriented
for (let j = 0; j < numNewPolys; j++) {
// Remove the groups for reversed polys
if (polyReversed[j]) {
polyGroups[j].length = 0;
}
// Polys inside the interior polys have their own groups, so remove
// them from this group
else if (polyGroups[j].length > 1) {
// Convert the group into a bit array, to make manipulation easier
innerPolys.length = 0;
for (let k = 1; k < polyGroups[j].length; k++) {
innerPolys[polyGroups[j][k]] = true;
}
// Look for non-reversed polys inside this one
for (let kk = 1; kk < polyGroups[j].length; kk++) {
// jj is the index of the inner poly
const jj = polyGroups[j][kk];
// If inner poly is not reversed then
if (!polyReversed[jj]) {
// Remove that poly and all polys inside of it from the group
for (let ii = 0; ii < polyGroups[jj].length; ii++) {
innerPolys[polyGroups[jj][ii]] = false;
}
}
}
// Use the bit array to recreate the polyGroup
polyGroups[j].length = 0;
polyGroups[j].push(j);
for (let jj = 0; jj < numNewPolys; jj++) {
if (innerPolys[jj]) {
polyGroups[j].push(jj);
}
}
}
}
}
// delete[] groupCount;
}
// ---------------------------------------------------
/**
* Check line segment with point Ids (i, j) to make sure that it
* doesn't cut through the edges of any polys in the group.
* Return value of zero means check failed and the cut is not
* usable.
*
* @param {Array[]} polys
* @param {vtkPoints} points
* @param {Vector3} normal
* @param {Array} polyGroup
* @param {Number} outerPolyId
* @param {Number} innerPolyId
* @param {Number} outerIdx
* @param {Number} innerIdx
*/
function vtkCCSCheckCut(polys, points, normal, polyGroup, outerPolyId, innerPolyId, outerIdx, innerIdx) {
const ptId1 = polys[outerPolyId][outerIdx];
const ptId2 = polys[innerPolyId][innerIdx];
const tol = CCS_POLYGON_TOLERANCE;
const p1 = [];
const p2 = [];
points.getPoint(ptId1, p1);
points.getPoint(ptId2, p2);
const w = [];
subtract(p2, p1, w);
const l = normalize(w);
// Cuts between coincident points are good
if (l === 0) {
return true;
}
// Define a tolerance with units of distance squared
const tol2 = l * l * tol * tol;
// Check the sense of the cut: it must be pointing "in" for both polys.
let polyId = outerPolyId;
let polyIdx = outerIdx;
let r = p1;
const r1 = [];
let r2 = p2;
const r3 = [];
for (let ii = 0; ii < 2; ii++) {
const poly = polys[polyId];
const n = poly.length;
let prevIdx = n - polyIdx - 1;
let nextIdx = polyIdx + 1;
if (prevIdx >= n) {
prevIdx -= n;
}
if (nextIdx >= n) {
nextIdx -= n;
}
points.getPoint(poly[prevIdx], r1);
points.getPoint(poly[nextIdx], r3);
if (vtkCCSVectorProgression(r, r1, r2, r3, normal) > 0) {
return false;
}
polyId = innerPolyId;
polyIdx = innerIdx;
r = p2;
r2 = p1;
}
// Check for intersections of the cut with polygon edges.
// First, create a cut plane that divides space at the cut line.
const pc = [];
cross(normal, w, pc);
pc[3] = -dot(pc, p1);
for (let i = 0; i < polyGroup.length; i++) {
const poly = polys[polyGroup[i]];
const n = poly.length;
const q1 = [];
const q2 = [];
let qtId1 = poly[n - 1];
points.getPoint(qtId1, q1);
let v1 = pc[0] * q1[0] + pc[1] * q1[1] + pc[2] * q1[2] + pc[3];
let c1 = v1 > 0;
for (let j = 0; j < n; j++) {
const qtId2 = poly[j];
points.getPoint(qtId2, q2);
const v2 = pc[0] * q2[0] + pc[1] * q2[1] + pc[2] * q2[2] + pc[3];
const c2 = v2 > 0;
// If lines share an endpoint, they can't intersect,
// so don't bother with the check.
if (ptId1 !== qtId1 && ptId1 !== qtId2 && ptId2 !== qtId1 && ptId2 !== qtId2) {
// Check for intersection
if ((c1 ? !c2 : c2) || v1 * v1 < tol2 || v2 * v2 < tol2) {
subtract(q2, q1, w);
if (dot(w, w) > 0) {
const qc = [];
cross(w, normal, qc);
qc[3] = -dot(qc, q1);
const u1 = qc[0] * p1[0] + qc[1] * p1[1] + qc[2] * p1[2] + qc[3];
const u2 = qc[0] * p2[0] + qc[1] * p2[1] + qc[2] * p2[2] + qc[3];
const d1 = u1 > 0;
const d2 = u2 > 0;
if (d1 ? !d2 : d2) {
// One final check to make sure endpoints aren't coincident
let p = p1;
let q = q1;
if (v2 * v2 < v1 * v1) {
p = p2;
}
if (u2 * u2 < u1 * u1) {
q = q2;
}
if (distance2BetweenPoints(p, q) > tol2) {
return false;
}
}
}
}
}
qtId1 = qtId2;
q1[0] = q2[0];
q1[1] = q2[1];
q1[2] = q2[2];
v1 = v2;
c1 = c2;
}
}
return true;
}
// ---------------------------------------------------
/**
* Check the quality of a cut between an outer and inner polygon.
* An ideal cut is one that forms a 90 degree angle with each
* line segment that it joins to. Smaller values indicate a
* higher quality cut.
*
* @param {Array} outerPoly
* @param {Array} innerPoly
* @param {Number} i
* @param {Number} j
* @param {vtkPoints} points
*/
function vtkCCSCutQuality(outerPoly, innerPoly, i, j, points) {
const n = outerPoly.length;
const m = innerPoly.length;
const a = i > 0 ? i - 1 : n - 1;
const b = i < n - 1 ? i + 1 : 0;
const c = j > 0 ? j - 1 : m - 1;
const d = j < m - 1 ? j + 1 : 0;
const p0 = [];
const p1 = [];
const p2 = [];
points.getPoint(outerPoly[i], p1);
points.getPoint(innerPoly[j], p2);
const v1 = [];
const v2 = [];
subtract(p2, p1, v1);
const l1 = dot(v1, v1);
let l2;
let qmax = 0;
let q;
points.getPoint(outerPoly[a], p0);
subtract(p0, p1, v2);
l2 = dot(v2, v2);
if (l2 > 0) {
q = dot(v1, v2);
q *= q / l2;
if (q > qmax) {
qmax = q;
}
}
points.getPoint(outerPoly[b], p0);
subtract(p0, p1, v2);
l2 = dot(v2, v2);
if (l2 > 0) {
q = dot(v1, v2);
q *= q / l2;
if (q > qmax) {
qmax = q;
}
}
points.getPoint(innerPoly[c], p0);
subtract(p2, p0, v2);
l2 = dot(v2, v2);
if (l2 > 0) {
q = dot(v1, v2);
q *= q / l2;
if (q > qmax) {
qmax = q;
}
}
points.getPoint(innerPoly[d], p0);
subtract(p2, p0, v2);
l2 = dot(v2, v2);
if (l2 > 0) {
q = dot(v1, v2);
q *= q / l2;
if (q > qmax) {
qmax = q;
}
}
if (l1 > 0) {
return qmax / l1; // also l1 + qmax, incorporates distance;
}
return Number.MAX_VALUE;
}
// ---------------------------------------------------
/**
* Find the two sharpest verts on an inner (i.e. inside-out) poly.
*
* @param {Array} poly
* @param {vtkPoints} points
* @param {Vector3} normal
* @param {[Number, Number]} verts
*/
function vtkCCSFindSharpestVerts(poly, points, normal, verts) {
const p1 = [];
const p2 = [];
const v1 = [];
const v2 = [];
const v = [];
let l1;
let l2;
const minVal = [0, 0];
verts[0] = 0;
verts[1] = 0;
const n = poly.length;
points.getPoint(poly[n - 1], p2);
points.getPoint(poly[0], p1);
subtract(p1, p2, v1);
l1 = Math.sqrt(dot(v1, v1));
for (let j = 0; j < n; j++) {
let k = j + 1;
if (k === n) {
k = 0;
}
points.getPoint(poly[k], p2);
subtract(p2, p1, v2);
l2 = Math.sqrt(dot(v2, v2));
cross(v1, v2, v);
const b = dot(v, normal);
if (b < 0 && l1 * l2 > 0) {
// Dot product is |v1||v2|cos(theta), range [-1, +1]
const val = dot(v1, v2) / (l1 * l2);
if (val < minVal[0]) {
minVal[1] = minVal[0];
minVal[0] = val;
verts[1] = verts[0];
verts[0] = j;
}
}
// Rotate to the next point
p1[0] = p2[0];
p1[1] = p2[1];
p1[2] = p2[2];
v1[0] = v2[0];
v1[1] = v2[1];
v1[2] = v2[2];
l1 = l2;
}
}
// ---------------------------------------------------
/**
* Find two valid cuts between outerPoly and innerPoly.
* Used by vtkCCSCutHoleyPolys.
*
* @param {Array} polys
* @param {Array} polyGroup
* @param {Number} outerPolyId
* @param {Number} innerPolyId
* @param {vtkPoints} points
* @param {Vector3} normal
* @param {Array[]} cuts
* @param {Boolean} exhaustive
*/
function vtkCCSFindCuts(polys, polyGroup, outerPolyId, innerPolyId, points, normal, cuts, exhaustive) {
const outerPoly = polys[outerPolyId];
const innerPoly = polys[innerPolyId];
const innerSize = innerPoly.length;
// Find the two sharpest points on the inner poly
const verts = [];
vtkCCSFindSharpestVerts(innerPoly, points, normal, verts);
// A list of cut locations according to quality
const cutlist = [];
cutlist.length = outerPoly.length;
// Search for potential cuts (need to find two cuts)
let cutId = 0;
cuts[0][0] = 0;
cuts[0][1] = 0;
cuts[1][0] = 0;
cuts[1][1] = 0;
let foundCut = false;
for (cutId = 0; cutId < 2; cutId++) {
const count = exhaustive ? innerSize : 3;
for (let i = 0; i < count && !foundCut; i++) {
// Semi-randomize the search order
// TODO: Does this do the same as in C++?
// eslint-disable-next-line no-bitwise
let j = (i >> 1) + (i & 1) * (innerSize + 1 >> 1);
// Start at the best first point
j = (j + verts[cutId]) % innerSize;
for (let kk = 0; kk < outerPoly.length; kk++) {
const q = vtkCCSCutQuality(outerPoly, innerPoly, kk, j, points);
cutlist[kk] = [q, kk];
}
cutlist.sort((a, b) => a[0] - b[0]);
for (let lid = 0; lid < cutlist.length; lid++) {
const k = cutlist[lid][1];
// If this is the second cut, do extra checks
if (cutId > 0) {
// Make sure cuts don't share an endpoint
if (j === cuts[0][1] || k === cuts[0][0]) {
// eslint-disable-next-line no-continue
continue;
}
// Make sure cuts don't intersect
const p1 = [];
const p2 = [];
points.getPoint(outerPoly[cuts[0][0]], p1);
points.getPoint(innerPoly[cuts[0][1]], p2);
const q1 = [];
const q2 = [];
points.getPoint(outerPoly[k], q1);
points.getPoint(innerPoly[j], q2);
let u;
let v;
if (vtkLine.intersection(p1, p2, q1, q2, u, v) === vtkLine.IntersectionState.YES_INTERSECTION) {
// eslint-disable-next-line no-continue
continue;
}
}
// This check is done for both cuts
if (vtkCCSCheckCut(polys, points, normal, polyGroup, outerPolyId, innerPolyId, k, j)) {
cuts[cutId][0] = k;
cuts[cutId][1] = j;
foundCut = true;
break;
}
}
}
if (!foundCut) {
return false;
}
}
return true;
}
// ---------------------------------------------------
/**
* Helper for vtkCCSCutHoleyPolys. Change a polygon and a hole
* into two separate polygons by making two cuts between them.
*
* @param {Array[]} polys
* @param {Array} polyEdges
* @param {Number} outerPolyId
* @param {Number} innerPolyId
* @param {vtkPoints} points
* @param {Array[]} cuts
*/
function vtkCCSMakeCuts(polys, polyEdges, outerPolyId, innerPolyId, points, cuts) {
const q = [];
const r = [];
for (let bb = 0; bb < 2; bb++) {
const ptId1 = polys[outerPolyId][cuts[bb][0]];
const ptId2 = polys[innerPolyId][cuts[bb][1]];
points.getPoint(ptId1, q);
points.getPoint(ptId2, r);
}
const outerPoly = polys[outerPolyId];
const innerPoly = polys[innerPolyId];
const outerEdges = polyEdges[outerPolyId];
const innerEdges = polyEdges[innerPolyId];
// Generate new polys from the cuts
const n = outerPoly.length;
const m = innerPoly.length;
let idx;
// Generate poly1
const n1 = n * (cuts[1][0] < cuts[0][0]) + cuts[1][0] - cuts[0][0] + 1;
const n2 = n1 + m * (cuts[0][1] < cuts[1][1]) + cuts[0][1] - cuts[1][1] + 1;
const poly1 = [];
poly1.length = n2;
const edges1 = new Array(n2);
idx = cuts[0][0];