mirror of
https://github.com/xibyte/jsketcher
synced 2025-12-06 08:25:19 +01:00
159 lines
No EOL
3.5 KiB
JavaScript
159 lines
No EOL
3.5 KiB
JavaScript
import {areEqual} from "math/equality";
|
|
|
|
export default function(outerLoop, innerLoops, tol) {
|
|
|
|
tol = tol || 1e-6;
|
|
|
|
function eq(a, b) {
|
|
return areEqual(a, b, tol);
|
|
}
|
|
|
|
function veq(a, b) {
|
|
return eq(a.x, b.x) && eq(a.y, b.y);
|
|
}
|
|
|
|
function classifyPointInsideLoop( pt, loop ) {
|
|
|
|
function VertexResult(vertex) {
|
|
this.inside = true;
|
|
this.vertex = vertex;
|
|
}
|
|
|
|
function EdgeResult(edge) {
|
|
this.inside = true;
|
|
this.edge = edge;
|
|
}
|
|
|
|
for( let pt2 of loop ) {
|
|
if (veq(pt, pt2)) {
|
|
return new VertexResult(pt2);
|
|
}
|
|
}
|
|
const grads = [];
|
|
const n = loop.length;
|
|
for (let i = 0; i < n; ++i) {
|
|
const j = (i + 1) % n;
|
|
const a = loop[i];
|
|
const b = loop[j];
|
|
let dy = b.y - a.y;
|
|
if (eq(dy, 0)) {
|
|
grads.push(0)
|
|
} else if (dy > 0) {
|
|
grads.push(1)
|
|
} else {
|
|
grads.push(-1)
|
|
}
|
|
}
|
|
|
|
function nextGrad(start) {
|
|
for(let i = 0; i < grads.length; ++i) {
|
|
const idx = (i + start + 1) % grads.length;
|
|
if (grads[idx] !== 0) {
|
|
return grads[idx];
|
|
}
|
|
}
|
|
}
|
|
|
|
function prevGrad(start) {
|
|
for(let i = 0; i < grads.length; ++i) {
|
|
const idx = (start - i - 1 + grads.length) % grads.length;
|
|
if (grads[idx] !== 0) {
|
|
return grads[idx];
|
|
}
|
|
}
|
|
}
|
|
|
|
const skip = new Set();
|
|
|
|
let inside = false;
|
|
for (let i = 0; i < n; ++i) {
|
|
const j = (i + 1) % n;
|
|
const a = loop[i];
|
|
const b = loop[j];
|
|
|
|
const shouldBeSkipped = skip.has(a) || skip.has(b);
|
|
|
|
const aEq = eq(pt.y, a.y);
|
|
const bEq = eq(pt.y, b.y);
|
|
|
|
if (aEq) {
|
|
skip.add(a);
|
|
}
|
|
if (bEq) {
|
|
skip.add(b);
|
|
}
|
|
|
|
if (veq(a, b)) {
|
|
console.error('unable to classify invalid polygon');
|
|
}
|
|
|
|
let edgeLowPt = a;
|
|
let edgeHighPt = b;
|
|
|
|
let edgeDx = edgeHighPt.x - edgeLowPt.x;
|
|
let edgeDy = edgeHighPt.y - edgeLowPt.y;
|
|
|
|
if (aEq && bEq) {
|
|
if ( ( ( edgeHighPt.x <= pt.x ) && ( pt.x <= edgeLowPt.x ) ) ||
|
|
( ( edgeLowPt.x <= pt.x ) && ( pt.x <= edgeHighPt.x ) ) ) {
|
|
return new EdgeResult([a, b]);
|
|
} else {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (shouldBeSkipped) {
|
|
continue;
|
|
}
|
|
|
|
if ( edgeDy < 0 ) {
|
|
edgeLowPt = b; edgeDx = - edgeDx;
|
|
edgeHighPt = a; edgeDy = - edgeDy;
|
|
}
|
|
if (!aEq && !bEq && ( pt.y < edgeLowPt.y || pt.y > edgeHighPt.y ) ) {
|
|
continue;
|
|
}
|
|
|
|
if (bEq) {
|
|
if (grads[i] * nextGrad(i) < 0) {
|
|
continue;
|
|
}
|
|
} else if (aEq) {
|
|
if (grads[i] * prevGrad(i) < 0) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
let perpEdge = edgeDx * (pt.y - edgeLowPt.y) - edgeDy * (pt.x - edgeLowPt.x);
|
|
if ( eq(perpEdge, 0) ) return new EdgeResult([a, b]);
|
|
if ( perpEdge < 0 ) {
|
|
continue;
|
|
}
|
|
inside = ! inside; // true intersection left of pt
|
|
}
|
|
return {inside};
|
|
}
|
|
|
|
return function classifyPointInsideLoops(pt) {
|
|
let outer = classifyPointInsideLoop(pt, outerLoop);
|
|
if (outer.inside) {
|
|
if (outer.vertex || outer.edge) {
|
|
return outer;
|
|
}
|
|
}
|
|
|
|
if (innerLoops) {
|
|
for (let innerLoop of innerLoops) {
|
|
const inner = classifyPointInsideLoop(pt, innerLoop);
|
|
if (inner.vertex || inner.edge) {
|
|
return inner;
|
|
}
|
|
if (inner.inside) {
|
|
return {inside: false};
|
|
}
|
|
}
|
|
}
|
|
|
|
return outer;
|
|
}
|
|
} |