import macro from 'vtk.js/Sources/macros'; import * as vtkMath from 'vtk.js/Sources/Common/Core/Math'; import vtkLine from 'vtk.js/Sources/Common/DataModel/Line'; import vtkPlane from 'vtk.js/Sources/Common/DataModel/Plane'; import vtkPriorityQueue from 'vtk.js/Sources/Common/Core/PriorityQueue'; import vtkBoundingBox from 'vtk.js/Sources/Common/DataModel/BoundingBox'; import { IntersectionState } from 'vtk.js/Sources/Common/DataModel/Line/Constants'; import { PolygonWithPointIntersectionState, EPSILON, FLOAT_EPSILON, TOLERANCE, } from './Constants';
function pointLocation(axis0, axis1, p0, p1, point) { return ( (p1[axis0] - p0[axis0]) * (point[axis1] - p0[axis1]) - (point[axis0] - p0[axis0]) * (p1[axis1] - p0[axis1]) ); }
function pointInPolygon(point, vertices, bounds, normal) { if ( point[0] < bounds[0] || point[0] > bounds[1] || point[1] < bounds[2] || point[1] > bounds[3] || point[2] < bounds[4] || point[2] > bounds[5] ) { return PolygonWithPointIntersectionState.OUTSIDE; }
if (vtkMath.normalize(normal) <= FLOAT_EPSILON) { return PolygonWithPointIntersectionState.FAILURE; }
let tol2 = TOLERANCE * ((bounds[1] - bounds[0]) * (bounds[1] - bounds[0]) + (bounds[3] - bounds[2]) * (bounds[3] - bounds[2]) + (bounds[5] - bounds[4]) * (bounds[5] - bounds[4])); tol2 *= tol2; tol2 = tol2 === 0.0 ? FLOAT_EPSILON : tol2; const p0 = []; const p1 = [];
for (let i = 0; i < vertices.length; ) { p0[0] = vertices[i++]; p0[1] = vertices[i++]; p0[2] = vertices[i++]; if (vtkMath.distance2BetweenPoints(point, p0) <= tol2) { return PolygonWithPointIntersectionState.INSIDE; }
const { distance, t } = vtkLine.distanceToLine(point, p0, p1); if (distance <= tol2 && t > 0.0 && t < 1.0) { return PolygonWithPointIntersectionState.INSIDE; } }
let axis0; let axis1; if (Math.abs(normal[0]) > Math.abs(normal[1])) { if (Math.abs(normal[0]) > Math.abs(normal[2])) { axis0 = 1; axis1 = 2; } else { axis0 = 0; axis1 = 1; } } else if (Math.abs(normal[1]) > Math.abs(normal[2])) { axis0 = 0; axis1 = 2; } else { axis0 = 0; axis1 = 1; }
let wn = 0; for (let i = 0; i < vertices.length; ) { p0[0] = vertices[i++]; p0[1] = vertices[i++]; p0[2] = vertices[i++]; if (i < vertices.length) { p1[0] = vertices[i]; p1[1] = vertices[i + 1]; p1[2] = vertices[i + 2]; } else { p1[0] = vertices[0]; p1[1] = vertices[1]; p1[2] = vertices[2]; }
if (p0[axis1] <= point[axis1]) { if (p1[axis1] > point[axis1]) { if (pointLocation(axis0, axis1, p0, p1, point) > 0) { ++wn; } } } else if (p1[axis1] <= point[axis1]) { if (pointLocation(axis0, axis1, p0, p1, point) < 0) { --wn; } } }
return wn === 0 ? PolygonWithPointIntersectionState.OUTSIDE : PolygonWithPointIntersectionState.INSIDE; }
export function getBounds(poly, points, bounds) { const n = poly.length; const p = [];
points.getPoint(poly[0], p); bounds[0] = p[0]; bounds[1] = p[0]; bounds[2] = p[1]; bounds[3] = p[1]; bounds[4] = p[2]; bounds[5] = p[2];
for (let j = 1; j < n; j++) { points.getPoint(poly[j], p); vtkBoundingBox.addPoint(bounds, ...p); } const length = vtkBoundingBox.getLengths(bounds); return vtkMath.dot(length, length); }
export function getNormal(poly, points, normal) { normal.length = 3; normal[0] = 0.0; normal[1] = 0.0; normal[2] = 0.0;
const p0 = []; let p1 = []; let p2 = []; const v1 = []; const v2 = [];
points.getPoint(poly[0], p0); points.getPoint(poly[1], p1);
for (let j = 2; j < poly.length; j++) { points.getPoint(poly[j], p2); vtkMath.subtract(p2, p1, v1); vtkMath.subtract(p0, p1, v2);
const n = [0, 0, 0]; vtkMath.cross(v1, v2, n); vtkMath.add(normal, n, normal);
[p1, p2] = [p2, p1]; }
return vtkMath.normalize(normal); }
const STATIC = { PolygonWithPointIntersectionState, pointInPolygon, getBounds, getNormal, };
function vtkPolygon(publicAPI, model) { model.classHierarchy.push('vtkPolygon');
function computeNormal() { const v1 = [0, 0, 0]; const v2 = [0, 0, 0]; model.normal = [0, 0, 0]; const anchor = [...model.firstPoint.point];
let point = model.firstPoint; for (let i = 0; i < model.pointCount; i++) { vtkMath.subtract(point.point, anchor, v1); vtkMath.subtract(point.next.point, anchor, v2);
const n = [0, 0, 0]; vtkMath.cross(v1, v2, n); vtkMath.add(model.normal, n, model.normal);
point = point.next; }
return vtkMath.normalize(model.normal); }
function computeMeasure(point) { const v1 = [0, 0, 0]; const v2 = [0, 0, 0]; const v3 = [0, 0, 0]; const v4 = [0, 0, 0];
vtkMath.subtract(point.point, point.previous.point, v1); vtkMath.subtract(point.next.point, point.point, v2); vtkMath.subtract(point.previous.point, point.next.point, v3); vtkMath.cross(v1, v2, v4);
const area = vtkMath.dot(v4, model.normal);
if (area <= 0) { return -1; }
const perimeter = vtkMath.norm(v1) + vtkMath.norm(v2) + vtkMath.norm(v3);
return (perimeter * perimeter) / area; }
function canRemoveVertex(point) { if (model.pointCount <= 3) { return true; }
const previous = point.previous; const next = point.next;
const v = [0, 0, 0]; vtkMath.subtract(next.point, previous.point, v);
const sN = [0, 0, 0]; vtkMath.cross(v, model.normal, sN); vtkMath.normalize(sN); if (vtkMath.norm(sN) === 0) { return false; }
let val = vtkPlane.evaluate(sN, previous.point, next.next.point); let currentSign = val > EPSILON ? 1 : val < -EPSILON ? -1 : 0; let oneNegative = currentSign < 0 ? 1 : 0;
for ( let vertex = next.next.next; vertex.id !== previous.id; vertex = vertex.next ) { const previousVertex = vertex.previous; val = vtkPlane.evaluate(sN, previous.point, vertex.point); const sign = val > EPSILON ? 1 : val < -EPSILON ? -1 : 0;
if (sign !== currentSign) { if (!oneNegative) { oneNegative = sign <= 0 ? 1 : 0; }
if ( vtkLine.intersection( previous.point, next.point, vertex.point, previousVertex.point, [0], [0] ) === IntersectionState.YES_INTERSECTION ) { return false; } currentSign = sign; } }
return oneNegative === 1; }
function removePoint(point, queue) { model.pointCount -= 1;
const previous = point.previous; const next = point.next;
model.tris = model.tris.concat(point.point); model.tris = model.tris.concat(next.point); model.tris = model.tris.concat(previous.point);
previous.next = next; next.previous = previous;
queue.deleteById(previous.id); queue.deleteById(next.id);
const previousMeasure = computeMeasure(previous); if (previousMeasure > 0) { queue.push(previousMeasure, previous); }
const nextMeasure = computeMeasure(next); if (nextMeasure > 0) { queue.push(nextMeasure, next); }
if (point.id === model.firstPoint.id) { model.firstPoint = next; } }
function earCutTriangulation() { computeNormal();
const vertexQueue = vtkPriorityQueue.newInstance(); let point = model.firstPoint; for (let i = 0; i < model.pointCount; i++) { const measure = computeMeasure(point); if (measure > 0) { vertexQueue.push(measure, point); }
point = point.next; }
while (model.pointCount > 2 && vertexQueue.length() > 0) { if (model.pointCount === vertexQueue.length()) { const pointToRemove = vertexQueue.pop(); removePoint(pointToRemove, vertexQueue); } else { const pointToRemove = vertexQueue.pop(); if (canRemoveVertex(pointToRemove)) { removePoint(pointToRemove, vertexQueue); } } }
return model.pointCount <= 2; }
publicAPI.triangulate = () => { if (!model.firstPoint) { return null; }
return earCutTriangulation(); };
publicAPI.setPoints = (points) => { model.pointCount = points.length;
model.firstPoint = { id: 0, point: points[0], next: null, previous: null, };
let currentPoint = model.firstPoint; for (let i = 1; i < model.pointCount; i++) { currentPoint.next = { id: i, point: points[i], next: null, previous: currentPoint, }; currentPoint = currentPoint.next; }
model.firstPoint.previous = currentPoint; currentPoint.next = model.firstPoint; };
publicAPI.getPointArray = () => model.tris; }
const DEFAULT_VALUES = { firstPoint: null, pointCount: 0, tris: [], };
export function extend(publicAPI, model, initialValues = {}) { Object.assign(model, DEFAULT_VALUES, initialValues);
macro.obj(publicAPI, model); vtkPolygon(publicAPI, model); }
export const newInstance = macro.newInstance(extend, 'vtkPolygon');
export default { newInstance, extend, ...STATIC };
|