Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions indexing.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
export type Intersection = import('./intersect.d.ts').Intersection & {
id1: number;
id2: number;
};
type Path = import('./intersect.d.ts').Path;
type PathComponent = import('./intersect.d.ts').PathComponent;

export type BBox = { x0: number; y0: number; x1: number; y1: number };

export type IndexEntry = {
pathId: number;
curveIndex: number;
curve: PathComponent;
};

export type IndexIntersection = [IndexEntry, IndexEntry];

export interface SpatialIndex {
add(
pathId: number,
curveIndex: number,
curve: PathComponent,
bbox: BBox
): void;

remove(pathId: number): void;

intersect(pathIds: number[]): IndexIntersection[];
}

/**
* Index {@link path} into {@link spatialIndex}
* Must be called before {@link findPathIntersections}
* @returns index key to pass to {@link findPathIntersections}
*/
export function indexPath(path: Path, spatialIndex: SpatialIndex): number;

/**
* Find or counts the intersections between two SVG paths.
*
* Returns a number in counting mode and a list of intersections otherwise.
*
* A single intersection entry contains the intersection coordinates (x, y)
* as well as additional information regarding the intersecting segments
* on each path (segment1, segment2) and the relative location of the
* intersection on these segments (t1, t2).
*
* The path may be an SVG path string or a list of path components
* such as `[ [ 'M', 0, 10 ], [ 'L', 20, 0 ] ]`.
*
* Uses spatial indexing to boost performance.
* If a path is not indexed the method will return no intersections.
* @see {@link indexPath}
*
* @example
*
* const spatialIndex = new SpatialIndex();
* const id1 = indexPath('M0,0L100,100', spatialIndex);
* const id2 = indexPath([ [ 'M', 0, 100 ], [ 'L', 100, 0 ] ], spatialIndex);
* const id3 = indexPath([ [ 'M', 0, 50 ], [ 'L', 100, 50 ] ], spatialIndex);
*
* const intersections = findPathIntersections(id1, id2, spatialIndex, false);
* const intersections2 = findPathIntersections(id1, id3, spatialIndex, false);
*
* // intersections = [
* // { x: 50, y: 50, segment1: 1, segment2: 1, t1: 0.5, t2: 0.5 }
* // ];
* // intersections2 = [
* // { x: 50, y: 50, segment1: 1, segment2: 1, t1: 0.5, t2: 0.5 }
* // ];
*/
declare function findPathIntersections(
pathIds: number[],
index: SpatialIndex,
justCount: true
): number;
declare function findPathIntersections(
pathIds: number[],
index: SpatialIndex,
justCount: false
): Intersection[];
declare function findPathIntersections(
pathIds: number[],
index: SpatialIndex
): Intersection[];
declare function findPathIntersections(
pathIds: number[],
index: SpatialIndex,
justCount?: boolean
): Intersection[] | number;

export default findPathIntersections;
81 changes: 81 additions & 0 deletions indexing.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { curveBBox, findBezierIntersections, parsePathCurve } from './intersect.js';

let indexKey = 0;

/**
*
* @param {string | import('./intersect').Path[]} path
* @param {import('./indexing').SpatialIndex} index
* @returns {number} index key
*/
export function indexPath(path, index) {
const curve = parsePathCurve(path);

const pathId = indexKey++;

for (
let curveIndex = 0, x1, y1, x1m, y1m, bez, pi;
curveIndex < curve.length;
curveIndex++
) {
pi = curve[curveIndex];

if (pi[0] == 'M') {
x1 = x1m = pi[1];
y1 = y1m = pi[2];
} else {
if (pi[0] == 'C') {
bez = [ x1, y1, ...pi.slice(1) ];
x1 = bez[6];
y1 = bez[7];
} else {
bez = [ x1, y1, x1, y1, x1m, y1m, x1m, y1m ];
x1 = x1m;
y1 = y1m;
}

index.add(pathId, curveIndex, bez, curveBBox(...bez));
}
}

return pathId;
}

/**
*
* @param {number[]} pathIds
* @param {import('./indexing').SpatialIndex} index
* @param {boolean} [justCount]
*/
export default function findIndexedPathIntersections(
pathIds,
index,
justCount
) {
let res = justCount ? 0 : [];

index.intersect(pathIds).forEach(([ a, b ]) => {

/**
* @type {import('./indexing.js').Intersection[]}
*/
const intr = findBezierIntersections(a.curve, b.curve, justCount);

if (justCount) {
res += intr;
} else {
for (var k = 0, kk = intr.length; k < kk; k++) {
intr[k].id1 = a.pathId;
intr[k].id2 = b.pathId;
intr[k].segment1 = a.curveIndex;
intr[k].segment2 = b.curveIndex;
intr[k].bez1 = a.curve;
intr[k].bez2 = b.curve;
}

res = res.concat(intr);
}
});

return res;
}
15 changes: 13 additions & 2 deletions intersect.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

/**
* Find or counts the intersections between two SVG paths.
*
Expand Down Expand Up @@ -36,6 +35,18 @@ declare function findPathIntersections(path1: Path, path2: Path, justCount?: boo

export default findPathIntersections;

/**
* Parse a path so it is suitable to pass to {@link findPathIntersections}
* Used in order to opt out of internal path caching.
*
* @example
* const p1 = parsePathCurve('M0,0L100,100');
* const p2 = parsePathCurve([ [ 'M', 0, 100 ], [ 'L', 100, 0 ] ]);
* const intersections = findPathIntersections(p1, p2);
*
*/
export declare function parsePathCurve(path: Path): PathComponent[]

/**
* A string in the form of 'M150,150m0,-18a18,18,0,1,1,0,36a18,18,0,1,1,0,-36z'
* or something like:
Expand All @@ -48,7 +59,7 @@ export default findPathIntersections;
* ]
*/
declare type Path = string | PathComponent[];
declare type PathComponent = any[];
declare type PathComponent = [cmd: string, ...number[]];

declare interface Intersection {
/**
Expand Down
Loading