Skip to content

Commit 8106fad

Browse files
committed
added FlatbushIndex
1 parent d2660d0 commit 8106fad

File tree

3 files changed

+86
-2
lines changed

3 files changed

+86
-2
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import Flatbush from "flatbush"
2+
3+
export interface ISpatialIndex<T> {
4+
insert(item: T, minX: number, minY: number, maxX: number, maxY: number): void
5+
finish(): void
6+
search(minX: number, minY: number, maxX: number, maxY: number): T[]
7+
clear(): void
8+
}
9+
10+
export class FlatbushIndex<T> implements ISpatialIndex<T> {
11+
private index: Flatbush
12+
private items: T[] = []
13+
private currentIndex = 0
14+
private capacity: number
15+
16+
constructor(numItems: number) {
17+
this.capacity = Math.max(1, numItems)
18+
this.index = new Flatbush(this.capacity)
19+
}
20+
21+
insert(item: T, minX: number, minY: number, maxX: number, maxY: number) {
22+
if (this.currentIndex >= this.index.numItems) {
23+
throw new Error("Exceeded initial capacity")
24+
}
25+
this.items[this.currentIndex] = item
26+
this.index.add(minX, minY, maxX, maxY)
27+
this.currentIndex++
28+
}
29+
30+
finish() {
31+
this.index.finish()
32+
}
33+
34+
search(minX: number, minY: number, maxX: number, maxY: number): T[] {
35+
const ids = this.index.search(minX, minY, maxX, maxY)
36+
return ids.map((id) => this.items[id] || null).filter(Boolean) as T[]
37+
}
38+
39+
clear() {
40+
this.items = []
41+
this.currentIndex = 0
42+
this.index = new Flatbush(this.capacity)
43+
}
44+
}

lib/solvers/GapFillSolver.ts

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { BaseSolver } from "@tscircuit/solver-utils"
22
import type { GraphicsObject } from "graphics-debug"
33
import type { SimpleRouteJson } from "../types/srj-types"
44
import type { Placed3D, XYRect } from "./rectdiff/types"
5+
import { FlatbushIndex } from "../data-structures/FlatbushIndex"
56

67
export interface RectEdge {
78
rect: XYRect
@@ -53,6 +54,7 @@ interface GapFillState {
5354
layerCount: number
5455

5556
edges: RectEdge[]
57+
edgeSpatialIndex: FlatbushIndex<RectEdge>
5658

5759
phase: SubPhase
5860
currentEdgeIndex: number
@@ -86,12 +88,16 @@ export class GapFillSolver extends BaseSolver {
8688

8789
const edges = this.extractEdges(input.placedRects)
8890

91+
// Build spatial index for fast edge-to-edge queries
92+
const edgeSpatialIndex = this.buildEdgeSpatialIndex(edges)
93+
8994
return {
9095
srj: input.simpleRouteJson,
9196
inputRects: input.placedRects,
9297
obstaclesByLayer: input.obstaclesByLayer,
9398
layerCount,
9499
edges,
100+
edgeSpatialIndex,
95101
phase: "SELECT_PRIMARY_EDGE",
96102
currentEdgeIndex: 0,
97103
nearbyEdgeCandidateIndex: 0,
@@ -103,6 +109,24 @@ export class GapFillSolver extends BaseSolver {
103109
}
104110
}
105111

112+
private buildEdgeSpatialIndex(edges: RectEdge[]): FlatbushIndex<RectEdge> {
113+
const index = new FlatbushIndex<RectEdge>(edges.length)
114+
115+
for (const edge of edges) {
116+
// Create bounding box for edge (padded by max search distance)
117+
const padding = 2.0 // Max edge distance threshold
118+
const minX = Math.min(edge.x1, edge.x2) - padding
119+
const minY = Math.min(edge.y1, edge.y2) - padding
120+
const maxX = Math.max(edge.x1, edge.x2) + padding
121+
const maxY = Math.max(edge.y1, edge.y2) + padding
122+
123+
index.insert(edge, minX, minY, maxX, maxY)
124+
}
125+
126+
index.finish()
127+
return index
128+
}
129+
106130
private extractEdges(rects: Placed3D[]): RectEdge[] {
107131
const edges: RectEdge[] = []
108132

@@ -216,8 +240,23 @@ export class GapFillSolver extends BaseSolver {
216240
private stepFindNearbyEdges(): void {
217241
const primaryEdge = this.state.currentPrimaryEdge!
218242

243+
// Query spatial index for candidate edges near this primary edge
244+
const padding = 2.0 // Max distance threshold
245+
const minX = Math.min(primaryEdge.x1, primaryEdge.x2) - padding
246+
const minY = Math.min(primaryEdge.y1, primaryEdge.y2) - padding
247+
const maxX = Math.max(primaryEdge.x1, primaryEdge.x2) + padding
248+
const maxY = Math.max(primaryEdge.y1, primaryEdge.y2) + padding
249+
250+
const candidates = this.state.edgeSpatialIndex.search(
251+
minX,
252+
minY,
253+
maxX,
254+
maxY,
255+
)
256+
257+
// Check only the nearby candidates (not all edges!)
219258
this.state.currentNearbyEdges = []
220-
for (const candidate of this.state.edges) {
259+
for (const candidate of candidates) {
221260
if (
222261
candidate !== primaryEdge &&
223262
this.isNearbyParallelEdge(primaryEdge, candidate)

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@
2929
"three": "^0.181.1",
3030
"tsup": "^8.5.1",
3131
"vite": "^6.0.11",
32-
"@vitejs/plugin-react": "^4"
32+
"@vitejs/plugin-react": "^4",
33+
"flatbush": "^4.5.0"
3334
},
3435
"peerDependencies": {
3536
"typescript": "^5"

0 commit comments

Comments
 (0)