open-plant 1.2.20 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -1
- package/README.md +129 -23
- package/dist/assets/point-hit-index-worker-Bp1uxxyQ.js +2 -0
- package/dist/assets/point-hit-index-worker-Bp1uxxyQ.js.map +1 -0
- package/dist/index.cjs +7 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2197 -2015
- package/dist/index.js.map +1 -1
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/react/overview-map.d.ts.map +1 -1
- package/dist/types/react/wsi-viewer-canvas.d.ts.map +1 -1
- package/dist/types/workers/point-hit-index-worker.d.ts +2 -0
- package/dist/types/workers/point-hit-index-worker.d.ts.map +1 -0
- package/dist/types/wsi/point-hit-index-worker-client.d.ts +18 -0
- package/dist/types/wsi/point-hit-index-worker-client.d.ts.map +1 -0
- package/dist/types/wsi/point-hit-index-worker-protocol.d.ts +30 -0
- package/dist/types/wsi/point-hit-index-worker-protocol.d.ts.map +1 -0
- package/dist/types/wsi/wsi-tile-renderer.d.ts +1 -0
- package/dist/types/wsi/wsi-tile-renderer.d.ts.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,7 +7,25 @@ and this project follows [Semantic Versioning](https://semver.org/).
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
### Added
|
|
11
|
+
- Web Worker 기반 포인트 공간 인덱스 빌드 (`point-hit-index-worker`). 메인 스레드 ~175ms 블로킹 제거.
|
|
12
|
+
- `FlatPointSpatialIndex` flat typed array 자료구조 + open-addressing hash table lookup.
|
|
13
|
+
- `buildPointSpatialIndexAsync()`, `lookupCellIndex()`, `terminatePointHitIndexWorker()` public API.
|
|
14
|
+
- 인접 tier (T±1) 타일 prefetch: 빠른 줌 시 blank frame 감소.
|
|
15
|
+
- `getVisibleTilesForTier(tier)` public method on `WsiTileRenderer`.
|
|
16
|
+
- 최적화 리포트: `perf-optimization-report.md`.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
- `pointHitIndex` 빌드가 `useMemo` (동기) → `useEffect` + `useState` (비동기 워커)로 전환.
|
|
20
|
+
- 워커 인덱스 알고리즘: nested `Map` → 4-pass counting sort (GC-free, typed arrays only).
|
|
21
|
+
- 워커 프로토콜: positions/ids를 워커가 반환하지 않음 — 메인 스레드가 원본 참조 (전송량 ~120MB → ~48MB).
|
|
22
|
+
- `getCellByCoordinates` 내부 lookup: nested `Map.get()` → `Int32Array` hash table O(1).
|
|
23
|
+
- 타일 스케줄링: 현재 tier 외 인접 tier 타일을 `distance2` 페널티 기반 낮은 우선순위로 포함.
|
|
24
|
+
- 내부 perf logging (`logPerf`, `shouldLogPerf`, `PERF_LOG_*`) 제거.
|
|
25
|
+
|
|
26
|
+
### Docs
|
|
27
|
+
- `performance-optimization.md` 업데이트: 워커 인덱스, prefetch, flat hash 내용 추가.
|
|
28
|
+
- `README.md` 프로젝트 구조: 워커/클라이언트 파일 반영.
|
|
11
29
|
|
|
12
30
|
## [1.2.4] - 2026-02-25
|
|
13
31
|
|
package/README.md
CHANGED
|
@@ -73,12 +73,19 @@ draw mode에 진입하면 `setPointerCapture`로 입력을 독점해 팬(드래
|
|
|
73
73
|
| | |
|
|
74
74
|
|---|---|
|
|
75
75
|
| **WebGL2 타일 렌더링** | 멀티 티어 타일 피라미드, LRU 캐시(320장), 저해상도 fallback 렌더링 |
|
|
76
|
+
| **타일 전용 색상 보정** | `imageColorSettings`로 brightness/contrast/saturation 실시간 반영 (cell/ROI/draw overlay는 영향 없음) |
|
|
76
77
|
| **회전 인터랙션** | `WsiViewState.rotationDeg`, `Ctrl/Cmd + drag` 회전, `resetRotation` 경로 |
|
|
78
|
+
| **줌 범위 제어 + 전환 애니메이션** | `minZoom`/`maxZoom` clamp + `viewTransition`(duration/easing) |
|
|
77
79
|
| **포인트 오버레이** | WebGL2 `gl.POINTS`로 수십, 수백만 개 포인트를 팔레트 텍스처 기반 컬러링. 파싱된 TypedArray만 입력 |
|
|
78
80
|
| **포인트 크기 커스터마이즈** | `pointSizeByZoom` 객체로 zoom별 셀(px) 크기 지정 + 내부 선형 보간 |
|
|
81
|
+
| **포인트 렌더 모드 제어** | `pointData.fillModes`로 ring/solid 렌더링 제어 |
|
|
79
82
|
| **모바일 타겟 성능** | iPhone 15급 환경에서 수백만 cell 워크로드를 전제로 pan/zoom 응답성을 유지하도록 설계 |
|
|
80
|
-
| **드로잉 / ROI 도구** | Freehand · Rectangle · Circular + Stamp(사각형/원, mm² 지정) |
|
|
83
|
+
| **드로잉 / ROI 도구** | Freehand · Rectangle · Circular · Brush + Stamp(사각형/원, mm² 지정) |
|
|
81
84
|
| **고정 픽셀 스탬프** | `stamp-rectangle-4096px` + `stampOptions.rectanglePixelSize` |
|
|
85
|
+
| **브러시 UX 제어** | `brushOptions` (`radius`, `edgeDetail`, `edgeSmoothing`, `clickSelectRoi` 등) |
|
|
86
|
+
| **ROI 인터랙션 제어** | `activeRegionId` controlled/uncontrolled + contour/label 기반 hit-test |
|
|
87
|
+
| **ROI 라벨 동적 제어** | `resolveRegionLabelStyle` + `autoLiftRegionLabelAtMaxZoom` |
|
|
88
|
+
| **실시간 면적 툴팁** | `drawAreaTooltip`으로 draw 중 mm² 표시 |
|
|
82
89
|
| **ROI 포인트 클리핑** | `clipMode`: `sync` / `worker` / `hybrid-webgpu` (실험) |
|
|
83
90
|
| **ROI 통계 API** | `computeRoiPointGroups()` + `onRoiPointGroups` 콜백 |
|
|
84
91
|
| **ROI 커스텀 오버레이** | `resolveRegionStrokeStyle`, `overlayShapes` |
|
|
@@ -111,11 +118,14 @@ src/
|
|
|
111
118
|
│ ├── point-clip.ts # ROI 포인트 클리핑
|
|
112
119
|
│ ├── point-clip-worker-client.ts # ROI 워커 클리핑 클라이언트
|
|
113
120
|
│ ├── point-clip-hybrid.ts # WebGPU + polygon 하이브리드 클리핑(실험)
|
|
121
|
+
│ ├── point-hit-index-worker-client.ts # 포인트 공간 인덱스 워커 클라이언트
|
|
122
|
+
│ ├── point-hit-index-worker-protocol.ts # 인덱스 워커 메시지 프로토콜
|
|
114
123
|
│ ├── webgpu.ts # WebGPU capability/compute 유틸
|
|
115
124
|
│ ├── image-info.ts # 이미지 메타데이터 정규화
|
|
116
125
|
│ └── utils.ts # 팔레트, 색상, 토큰 유틸리티
|
|
117
126
|
├── workers/
|
|
118
|
-
│
|
|
127
|
+
│ ├── roi-clip-worker.ts # ROI point-in-polygon worker
|
|
128
|
+
│ └── point-hit-index-worker.ts # 포인트 공간 인덱스 빌드 worker
|
|
119
129
|
└── react/ # React 컴포넌트
|
|
120
130
|
├── wsi-viewer-canvas.tsx # 전체 기능 WSI 뷰어
|
|
121
131
|
├── draw-layer.tsx # 드로잉 오버레이
|
|
@@ -126,7 +136,7 @@ src/
|
|
|
126
136
|
|
|
127
137
|
### `<WsiViewerCanvas>`
|
|
128
138
|
|
|
129
|
-
전체 기능을 갖춘 WSI 뷰어 컴포넌트.
|
|
139
|
+
전체 기능을 갖춘 WSI 뷰어 컴포넌트. 실사용 시 대부분의 기능은 이 컴포넌트 하나로 제어합니다.
|
|
130
140
|
|
|
131
141
|
```jsx
|
|
132
142
|
import { WsiViewerCanvas } from "open-plant";
|
|
@@ -137,8 +147,8 @@ import { WsiViewerCanvas } from "open-plant";
|
|
|
137
147
|
imageColorSettings={{ brightness: 0, contrast: 0, saturation: 0 }}
|
|
138
148
|
ctrlDragRotate
|
|
139
149
|
rotationResetNonce={rotationResetNonce}
|
|
140
|
-
minZoom={0.25}
|
|
141
|
-
maxZoom={
|
|
150
|
+
minZoom={0.25} // 미지정 시 fitZoom * 0.5
|
|
151
|
+
maxZoom={1} // 미지정 시 fitZoom * 8
|
|
142
152
|
viewTransition={{ duration: 300 }}
|
|
143
153
|
authToken={bearerToken}
|
|
144
154
|
pointData={pointPayload}
|
|
@@ -152,7 +162,7 @@ import { WsiViewerCanvas } from "open-plant";
|
|
|
152
162
|
clipPointsToRois
|
|
153
163
|
clipMode="worker"
|
|
154
164
|
onClipStats={(s) => console.log(s.mode, s.durationMs)}
|
|
155
|
-
drawTool=
|
|
165
|
+
drawTool={drawTool}
|
|
156
166
|
drawFillColor="transparent"
|
|
157
167
|
activeRegionId={selectedRoiId} // controlled: 외부에서 active ROI 제어
|
|
158
168
|
onActiveRegionChange={setSelectedRoiId} // 내부 클릭/탭 선택 변경 알림
|
|
@@ -190,6 +200,7 @@ import { WsiViewerCanvas } from "open-plant";
|
|
|
190
200
|
onRoiPointGroups={(stats) => console.log(stats.groups)}
|
|
191
201
|
onDrawComplete={(result) => {
|
|
192
202
|
if (result.intent === "roi") handleRoi(result);
|
|
203
|
+
if (result.intent === "brush") handleBrush(result);
|
|
193
204
|
}}
|
|
194
205
|
onPatchComplete={(patch) => {
|
|
195
206
|
// stamp-rectangle-4096px 전용
|
|
@@ -200,23 +211,118 @@ import { WsiViewerCanvas } from "open-plant";
|
|
|
200
211
|
/>
|
|
201
212
|
```
|
|
202
213
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
`
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
`
|
|
209
|
-
`
|
|
210
|
-
`
|
|
211
|
-
`
|
|
212
|
-
`
|
|
213
|
-
`
|
|
214
|
-
`
|
|
215
|
-
|
|
216
|
-
`
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
214
|
+
#### 동작 규약 (중요)
|
|
215
|
+
|
|
216
|
+
- `mpp`(microns per pixel)는 스탬프 mm² 환산에 사용됩니다. 미지정 시 물리 크기는 근사치입니다.
|
|
217
|
+
- `imageColorSettings`는 타일 레이어에만 적용됩니다. 포인트/ROI/드로잉은 영향받지 않습니다.
|
|
218
|
+
- ROI hit-test는 **contour + nametag 영역** 기준입니다. ROI 내부 fill은 클릭/hover 영역에서 제외됩니다.
|
|
219
|
+
- `activeRegionId`를 주면 controlled mode, 생략하면 uncontrolled mode로 동작합니다.
|
|
220
|
+
- `minZoom`/`maxZoom`은 휠/더블클릭/`setViewState`/`fitToImage` 전 경로에 동일 clamp가 적용됩니다.
|
|
221
|
+
- `viewTransition`은 `setViewState`/`fitToImage`/`zoomBy` 전환에 적용되며 `duration` 최대값은 `2000ms`입니다.
|
|
222
|
+
- `drawFillColor` 기본값은 `transparent`입니다.
|
|
223
|
+
- `brushOptions.radius`는 HTML/CSS px 기준이며, 줌이 바뀌어도 on-screen 크기는 고정됩니다.
|
|
224
|
+
- `brushOptions.clickSelectRoi=true`이면 브러시 탭(드래그 없음) 시 ROI를 먼저 선택하고, ROI 외부 탭은 일반 브러시 결과를 반환합니다.
|
|
225
|
+
- `autoLiftRegionLabelAtMaxZoom=true`이면 `maxZoom` 도달 시 라벨이 위로 `20px` 애니메이션 이동하고, 이탈 시 원위치로 내려옵니다.
|
|
226
|
+
- `drawAreaTooltip.enabled=true`이면 freehand/rectangle/circular 그리기 중 커서 근처에 실시간 면적(mm²)을 표시합니다.
|
|
227
|
+
- `roiRegions[].coordinates`는 ring / polygon(with holes) / multipolygon을 모두 지원합니다.
|
|
228
|
+
|
|
229
|
+
#### WsiViewerCanvas Props By Concern
|
|
230
|
+
|
|
231
|
+
**View / Camera**
|
|
232
|
+
|
|
233
|
+
| Prop | Type | Notes |
|
|
234
|
+
|---|---|---|
|
|
235
|
+
| `source` | `WsiImageSource \| null` | 필수 입력 메타데이터 |
|
|
236
|
+
| `viewState` | `Partial<WsiViewState> \| null` | 외부 제어 시점 |
|
|
237
|
+
| `onViewStateChange` | `(next) => void` | 내부 변경 통지 |
|
|
238
|
+
| `fitNonce` | `number` | 변경 시 fit 재실행 |
|
|
239
|
+
| `rotationResetNonce` | `number` | 변경 시 회전 0도 |
|
|
240
|
+
| `ctrlDragRotate` | `boolean` | 기본 `true` |
|
|
241
|
+
| `minZoom` / `maxZoom` | `number` | 미지정 시 `fitZoom*0.5` / `fitZoom*8` |
|
|
242
|
+
| `viewTransition` | `{ duration?: number; easing?: (t)=>number }` | 기본 즉시 반영(duration 0) |
|
|
243
|
+
| `authToken` | `string` | 타일/포인트 요청 인증 |
|
|
244
|
+
| `overviewMapConfig` | `OverviewMapConfig` | 미니맵 표시/옵션 |
|
|
245
|
+
|
|
246
|
+
**Tile / Point / Clip**
|
|
247
|
+
|
|
248
|
+
| Prop | Type | Notes |
|
|
249
|
+
|---|---|---|
|
|
250
|
+
| `imageColorSettings` | `WsiImageColorSettings \| null` | brightness/contrast/saturation 입력 범위 `[-100, 100]` |
|
|
251
|
+
| `pointData` | `WsiPointData \| null` | `positions`, `paletteIndices` 필수 |
|
|
252
|
+
| `pointPalette` | `Uint8Array \| null` | RGBA 팔레트 텍스처 |
|
|
253
|
+
| `pointSizeByZoom` | `Record<number, number>` | continuous zoom stop |
|
|
254
|
+
| `pointStrokeScale` | `number` | point ring 두께 스케일 |
|
|
255
|
+
| `clipPointsToRois` | `boolean` | ROI 외부 포인트 필터 |
|
|
256
|
+
| `clipMode` | `"sync" \| "worker" \| "hybrid-webgpu"` | 기본 `"worker"` |
|
|
257
|
+
| `onClipStats` | `(event) => void` | clip 실행 통계 |
|
|
258
|
+
| `onRoiPointGroups` | `(stats) => void` | ROI term 통계 |
|
|
259
|
+
| `roiPaletteIndexToTermId` | `ReadonlyMap<number,string> \| readonly string[]` | ROI term 매핑 |
|
|
260
|
+
|
|
261
|
+
**ROI / Draw / Overlay**
|
|
262
|
+
|
|
263
|
+
| Prop | Type | Notes |
|
|
264
|
+
|---|---|---|
|
|
265
|
+
| `roiRegions` / `roiPolygons` | `WsiRegion[]` / `DrawRegionCoordinates[]` | 영속 ROI 입력 |
|
|
266
|
+
| `patchRegions` | `WsiRegion[]` | patch 전용 표시 채널 |
|
|
267
|
+
| `interactionLock` | `boolean` | pan/zoom 잠금 |
|
|
268
|
+
| `drawTool` | `DrawTool` | 기본 `"cursor"` |
|
|
269
|
+
| `stampOptions` | `StampOptions` | mm² / 고정 px stamp 크기 |
|
|
270
|
+
| `brushOptions` | `BrushOptions` | 브러시 궤적/커서/탭 선택 |
|
|
271
|
+
| `drawFillColor` | `string` | draw preview fill, 기본 `transparent` |
|
|
272
|
+
| `regionStrokeStyle` / `regionStrokeHoverStyle` / `regionStrokeActiveStyle` | `Partial<RegionStrokeStyle>` | ROI 외곽선 스타일 |
|
|
273
|
+
| `patchStrokeStyle` | `Partial<RegionStrokeStyle>` | patch 선 스타일 |
|
|
274
|
+
| `resolveRegionStrokeStyle` | `RegionStrokeStyleResolver` | 상태별 동적 stroke |
|
|
275
|
+
| `regionLabelStyle` | `Partial<RegionLabelStyle>` | 기본 배지 스타일 override |
|
|
276
|
+
| `resolveRegionLabelStyle` | `RegionLabelStyleResolver` | 줌/region별 동적 라벨 스타일 |
|
|
277
|
+
| `autoLiftRegionLabelAtMaxZoom` | `boolean` | max zoom 도달 시 라벨 auto-lift |
|
|
278
|
+
| `drawAreaTooltip` | `DrawAreaTooltipOptions` | draw 중 실시간 mm² tooltip |
|
|
279
|
+
| `overlayShapes` | `DrawOverlayShape[]` | 커스텀 도형/반전 마스크 |
|
|
280
|
+
| `customLayers` | `WsiCustomLayer[]` | host React 오버레이 슬롯 |
|
|
281
|
+
| `activeRegionId` | `string \| number \| null` | controlled active ROI |
|
|
282
|
+
|
|
283
|
+
**Events / Refs**
|
|
284
|
+
|
|
285
|
+
| Prop | Type | Notes |
|
|
286
|
+
|---|---|---|
|
|
287
|
+
| `onStats` | `(stats: WsiRenderStats) => void` | 프레임 통계 |
|
|
288
|
+
| `onTileError` | `(event: WsiTileErrorEvent) => void` | 타일 로드 실패 |
|
|
289
|
+
| `onContextLost` / `onContextRestored` | `() => void` | WebGL 컨텍스트 이벤트 |
|
|
290
|
+
| `onPointerWorldMove` | `(event) => void` | world 좌표 포인터 스트림 |
|
|
291
|
+
| `onPointHover` / `onPointClick` | `(event) => void` | 포인트 hit 이벤트 |
|
|
292
|
+
| `getCellByCoordinatesRef` | `MutableRefObject<(coord)=>PointHitEvent \| null>` | imperative 좌표 hit-test |
|
|
293
|
+
| `onRegionHover` / `onRegionClick` | `(event) => void` | region hit 이벤트 |
|
|
294
|
+
| `onActiveRegionChange` | `(regionId) => void` | active 변경 통지 |
|
|
295
|
+
| `onDrawComplete` | `(result: DrawResult) => void` | `intent: "roi" \| "patch" \| "brush"` |
|
|
296
|
+
| `onPatchComplete` | `(result: PatchDrawResult) => void` | `stamp-rectangle-4096px` 전용 |
|
|
297
|
+
| `className` / `style` | `string` / `CSSProperties` | 컨테이너 스타일 |
|
|
298
|
+
|
|
299
|
+
### `<DrawLayer>`
|
|
300
|
+
|
|
301
|
+
독립 오버레이 드로잉 컴포넌트입니다. `WsiViewerCanvas` 내부에서 자동 사용되지만, 필요하면 별도로 직접 사용할 수 있습니다.
|
|
302
|
+
|
|
303
|
+
- 지원 툴: `freehand`, `rectangle`, `circular`, `brush`, `stamp-*`
|
|
304
|
+
- 브러시는 화면 픽셀 기준 반경 + `edgeDetail`/`edgeSmoothing` 옵션을 사용합니다.
|
|
305
|
+
- `Esc`로 현재 드로잉 세션을 취소할 수 있습니다.
|
|
306
|
+
|
|
307
|
+
### `<OverviewMap>`
|
|
308
|
+
|
|
309
|
+
현재 뷰포트를 표시하는 인터랙티브 미니맵입니다. `overviewMapConfig.show`를 `true`로 설정하면 `WsiViewerCanvas`에 함께 렌더링됩니다.
|
|
310
|
+
|
|
311
|
+
## API
|
|
312
|
+
|
|
313
|
+
| Export | 설명 |
|
|
314
|
+
|---|---|
|
|
315
|
+
| `WsiViewerCanvas`, `DrawLayer`, `OverviewMap`, `TileViewerCanvas` | React 컴포넌트 |
|
|
316
|
+
| `WsiTileRenderer`, `M1TileRenderer`, `TileScheduler` | 렌더러/스케줄러 클래스 |
|
|
317
|
+
| `normalizeImageInfo`, `toTileUrl` | 이미지 메타데이터/타일 URL 유틸 |
|
|
318
|
+
| `buildTermPalette`, `calcScaleResolution`, `calcScaleLength`, `toBearerToken` | 공통 유틸 |
|
|
319
|
+
| `filterPointDataByPolygons`, `filterPointDataByPolygonsInWorker`, `filterPointDataByPolygonsHybrid` | ROI 포인트 클리핑 |
|
|
320
|
+
| `filterPointIndicesByPolygons`, `filterPointIndicesByPolygonsInWorker`, `terminateRoiClipWorker` | 인덱스 기반 클리핑/워커 관리 |
|
|
321
|
+
| `buildPointSpatialIndexAsync`, `lookupCellIndex`, `terminatePointHitIndexWorker` | 포인트 공간 인덱스 (워커) |
|
|
322
|
+
| `computeRoiPointGroups` | ROI term 통계 |
|
|
323
|
+
| `getWebGpuCapabilities`, `prefilterPointsByBoundsWebGpu` | WebGPU capability/연산(실험) |
|
|
324
|
+
| `closeRing`, `createRectangle`, `createCircle` | 도형 유틸 |
|
|
325
|
+
| 타입 export (`WsiViewerCanvasProps`, `WsiImageSource`, `WsiPointData`, `WsiViewTransitionOptions` 등) | TypeScript 통합용 공개 타입 |
|
|
220
326
|
|
|
221
327
|
### `<DrawLayer>`
|
|
222
328
|
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
(function(){"use strict";function U(){return typeof performance<"u"&&typeof performance.now=="function"?performance.now():Date.now()}function q(r){if(r instanceof Error)return r.message;try{return String(r)}catch{return"unknown worker error"}}function X(r,f,o){if(r<=0||f<=0||o<=0)return 256;const i=Math.max(1,r*f),h=Math.sqrt(i/Math.max(1,o))*4;return Math.max(24,Math.min(1024,h))}function S(r,f,o){return(r*73856093^f*19349663)>>>0&o}function B(r){const f=U(),o=Math.max(0,Math.floor(r.count)),i=new Float32Array(r.positions),L=Math.floor(i.length/2),h=Math.max(0,Math.min(o,L));if(h<=0)return null;let I=null;if(r.drawIndices){const t=new Uint32Array(r.drawIndices);if(t.length>0){let n=!0;for(let s=0;s<t.length;s+=1)if(t[s]>=h){n=!1;break}if(n)I=t;else{const s=new Uint32Array(t.length);let e=0;for(let u=0;u<t.length;u+=1)t[u]<h&&(s[e++]=t[u]);I=e>0?s.subarray(0,e):null}}}const A=I?I.length:h;if(A===0)return null;const F=X(r.sourceWidth,r.sourceHeight,A),H=1/F,O=new Int32Array(A),P=new Int32Array(A);let c=0;if(I)for(let t=0;t<A;t+=1){const n=I[t],s=i[n*2],e=i[n*2+1];!Number.isFinite(s)||!Number.isFinite(e)||(O[c]=Math.floor(s*H),P[c]=Math.floor(e*H),c+=1)}else for(let t=0;t<h;t+=1){const n=i[t*2],s=i[t*2+1];!Number.isFinite(n)||!Number.isFinite(s)||(O[c]=Math.floor(n*H),P[c]=Math.floor(s*H),c+=1)}if(c===0)return null;const V=Math.min(c,Math.max(64,c>>>3));let a=1;for(;a<V*2;)a<<=1;const G=a-1;let l=new Int32Array(a*2),p=new Int32Array(a);l.fill(2147483647);let y=0;const _=new Int32Array(c);for(let t=0;t<c;t+=1){const n=O[t],s=P[t];let e=S(n,s,G);for(;;){const u=l[e*2];if(u===2147483647){if(l[e*2]=n,l[e*2+1]=s,p[e]=1,_[t]=e,y+=1,y*4>a*3){const j=a;a<<=1;const E=a-1,x=new Int32Array(a*2),v=new Int32Array(a);x.fill(2147483647);for(let d=0;d<j;d+=1){if(l[d*2]===2147483647)continue;const z=l[d*2],Z=l[d*2+1];let M=S(z,Z,E);for(;x[M*2]!==2147483647;)M=M+1&E;x[M*2]=z,x[M*2+1]=Z,v[M]=p[d]}for(l=x,p=v,e=S(n,s,E);l[e*2]!==n||l[e*2+1]!==s;)e=e+1&E;_[t]=e}break}if(u===n&&l[e*2+1]===s){p[e]+=1,_[t]=e;break}e=e+1&G}}const b=new Int32Array(y*2),k=new Uint32Array(y),K=new Uint32Array(y),g=new Int32Array(a);g.fill(-1);let w=0,R=0;for(let t=0;t<a;t+=1)l[t*2]!==2147483647&&(b[w*2]=l[t*2],b[w*2+1]=l[t*2+1],k[w]=R,K[w]=p[t],g[t]=w,R+=p[t],w+=1);const D=new Uint32Array(c),T=new Uint32Array(y);if(T.set(k),I)for(let t=0;t<c;t+=1){const n=g[_[t]];D[T[n]]=I[t],T[n]+=1}else{let t=0;for(let n=0;n<h;n+=1){const s=i[n*2],e=i[n*2+1];if(!Number.isFinite(s)||!Number.isFinite(e))continue;const u=g[_[t]];D[T[u]]=n,T[u]+=1,t+=1}}let C=1;for(;C<y*2;)C<<=1;const Y=C-1,N=new Int32Array(C);N.fill(-1);for(let t=0;t<y;t+=1){const n=b[t*2],s=b[t*2+1];let e=S(n,s,Y);for(;N[e]!==-1;)e=e+1&Y;N[e]=t}return{type:"point-hit-index-success",id:r.id,cellSize:F,safeCount:h,cellCount:y,hashCapacity:C,hashTable:N.buffer,cellKeys:b.buffer,cellOffsets:k.buffer,cellLengths:K.buffer,pointIndices:D.buffer,durationMs:U()-f}}const m=self;m.addEventListener("message",r=>{const f=r.data;if(!(!f||f.type!=="point-hit-index-request"))try{const o=B(f);if(!o){const i={type:"point-hit-index-success",id:f.id,cellSize:0,safeCount:0,cellCount:0,hashCapacity:0,hashTable:new Int32Array(0).buffer,cellKeys:new Int32Array(0).buffer,cellOffsets:new Uint32Array(0).buffer,cellLengths:new Uint32Array(0).buffer,pointIndices:new Uint32Array(0).buffer,durationMs:0};m.postMessage(i,[i.hashTable,i.cellKeys,i.cellOffsets,i.cellLengths,i.pointIndices]);return}m.postMessage(o,[o.hashTable,o.cellKeys,o.cellOffsets,o.cellLengths,o.pointIndices])}catch(o){const i={type:"point-hit-index-failure",id:f.id,error:q(o)};m.postMessage(i)}})})();
|
|
2
|
+
//# sourceMappingURL=point-hit-index-worker-Bp1uxxyQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"point-hit-index-worker-Bp1uxxyQ.js","sources":["../src/workers/point-hit-index-worker.ts"],"sourcesContent":["import type {\n\tPointHitIndexWorkerRequest,\n\tPointHitIndexWorkerResponse,\n\tPointHitIndexWorkerSuccess,\n} from \"../wsi/point-hit-index-worker-protocol\";\n\nconst MIN_POINT_HIT_GRID_SIZE = 24;\nconst MAX_POINT_HIT_GRID_SIZE = 1024;\nconst POINT_HIT_GRID_DENSITY_SCALE = 4;\nconst HASH_EMPTY = -1;\n\nfunction nowMs(): number {\n\tif (typeof performance !== \"undefined\" && typeof performance.now === \"function\") {\n\t\treturn performance.now();\n\t}\n\treturn Date.now();\n}\n\nfunction toErrorMessage(error: unknown): string {\n\tif (error instanceof Error) return error.message;\n\ttry {\n\t\treturn String(error);\n\t} catch {\n\t\treturn \"unknown worker error\";\n\t}\n}\n\nfunction resolveGridSize(sourceWidth: number, sourceHeight: number, visibleCount: number): number {\n\tif (sourceWidth <= 0 || sourceHeight <= 0 || visibleCount <= 0) return 256;\n\tconst area = Math.max(1, sourceWidth * sourceHeight);\n\tconst avgSpacing = Math.sqrt(area / Math.max(1, visibleCount));\n\tconst raw = avgSpacing * POINT_HIT_GRID_DENSITY_SCALE;\n\treturn Math.max(MIN_POINT_HIT_GRID_SIZE, Math.min(MAX_POINT_HIT_GRID_SIZE, raw));\n}\n\nfunction cellHash(cellX: number, cellY: number, mask: number): number {\n\treturn (((cellX * 73856093) ^ (cellY * 19349663)) >>> 0) & mask;\n}\n\nfunction handleRequest(msg: PointHitIndexWorkerRequest): PointHitIndexWorkerSuccess | null {\n\tconst start = nowMs();\n\tconst count = Math.max(0, Math.floor(msg.count));\n\tconst positions = new Float32Array(msg.positions);\n\tconst maxCountByPositions = Math.floor(positions.length / 2);\n\tconst safeCount = Math.max(0, Math.min(count, maxCountByPositions));\n\n\tif (safeCount <= 0) return null;\n\n\tlet drawIndices: Uint32Array | null = null;\n\tif (msg.drawIndices) {\n\t\tconst raw = new Uint32Array(msg.drawIndices);\n\t\tif (raw.length > 0) {\n\t\t\tlet allValid = true;\n\t\t\tfor (let i = 0; i < raw.length; i += 1) {\n\t\t\t\tif (raw[i] >= safeCount) { allValid = false; break; }\n\t\t\t}\n\t\t\tif (allValid) {\n\t\t\t\tdrawIndices = raw;\n\t\t\t} else {\n\t\t\t\tconst filtered = new Uint32Array(raw.length);\n\t\t\t\tlet cursor = 0;\n\t\t\t\tfor (let i = 0; i < raw.length; i += 1) {\n\t\t\t\t\tif (raw[i] < safeCount) { filtered[cursor++] = raw[i]; }\n\t\t\t\t}\n\t\t\t\tdrawIndices = cursor > 0 ? filtered.subarray(0, cursor) : null;\n\t\t\t}\n\t\t}\n\t}\n\n\tconst visibleCount = drawIndices ? drawIndices.length : safeCount;\n\tif (visibleCount === 0) return null;\n\n\tconst cellSize = resolveGridSize(msg.sourceWidth, msg.sourceHeight, visibleCount);\n\tconst invCellSize = 1.0 / cellSize;\n\n\t// --- Pass 1: assign cell keys, count unique cells ---\n\tconst pointCellX = new Int32Array(visibleCount);\n\tconst pointCellY = new Int32Array(visibleCount);\n\tlet validCount = 0;\n\n\tif (drawIndices) {\n\t\tfor (let i = 0; i < visibleCount; i += 1) {\n\t\t\tconst pi = drawIndices[i];\n\t\t\tconst px = positions[pi * 2];\n\t\t\tconst py = positions[pi * 2 + 1];\n\t\t\tif (!Number.isFinite(px) || !Number.isFinite(py)) continue;\n\t\t\tpointCellX[validCount] = Math.floor(px * invCellSize);\n\t\t\tpointCellY[validCount] = Math.floor(py * invCellSize);\n\t\t\tvalidCount += 1;\n\t\t}\n\t} else {\n\t\tfor (let i = 0; i < safeCount; i += 1) {\n\t\t\tconst px = positions[i * 2];\n\t\t\tconst py = positions[i * 2 + 1];\n\t\t\tif (!Number.isFinite(px) || !Number.isFinite(py)) continue;\n\t\t\tpointCellX[validCount] = Math.floor(px * invCellSize);\n\t\t\tpointCellY[validCount] = Math.floor(py * invCellSize);\n\t\t\tvalidCount += 1;\n\t\t}\n\t}\n\n\tif (validCount === 0) return null;\n\n\t// --- Pass 2: build temporary hash to discover unique cells and count per cell ---\n\tconst estimatedCells = Math.min(validCount, Math.max(64, validCount >>> 3));\n\tlet hashCapacity = 1;\n\twhile (hashCapacity < estimatedCells * 2) hashCapacity <<= 1;\n\tconst hashMask = hashCapacity - 1;\n\n\tlet tempHashKeys = new Int32Array(hashCapacity * 2);\n\tlet tempHashCounts = new Int32Array(hashCapacity);\n\ttempHashKeys.fill(0x7FFFFFFF);\n\tlet cellCount = 0;\n\n\tconst pointCellSlot = new Int32Array(validCount);\n\n\tfor (let i = 0; i < validCount; i += 1) {\n\t\tconst cx = pointCellX[i];\n\t\tconst cy = pointCellY[i];\n\t\tlet slot = cellHash(cx, cy, hashMask);\n\n\t\twhile (true) {\n\t\t\tconst kx = tempHashKeys[slot * 2];\n\t\t\tif (kx === 0x7FFFFFFF) {\n\t\t\t\ttempHashKeys[slot * 2] = cx;\n\t\t\t\ttempHashKeys[slot * 2 + 1] = cy;\n\t\t\t\ttempHashCounts[slot] = 1;\n\t\t\t\tpointCellSlot[i] = slot;\n\t\t\t\tcellCount += 1;\n\t\t\t\tif (cellCount * 4 > hashCapacity * 3) {\n\t\t\t\t\tconst oldCap = hashCapacity;\n\t\t\t\t\thashCapacity <<= 1;\n\t\t\t\t\tconst newMask = hashCapacity - 1;\n\t\t\t\t\tconst newKeys = new Int32Array(hashCapacity * 2);\n\t\t\t\t\tconst newCounts = new Int32Array(hashCapacity);\n\t\t\t\t\tnewKeys.fill(0x7FFFFFFF);\n\t\t\t\t\tfor (let s = 0; s < oldCap; s += 1) {\n\t\t\t\t\t\tif (tempHashKeys[s * 2] === 0x7FFFFFFF) continue;\n\t\t\t\t\t\tconst ocx = tempHashKeys[s * 2];\n\t\t\t\t\t\tconst ocy = tempHashKeys[s * 2 + 1];\n\t\t\t\t\t\tlet ns = cellHash(ocx, ocy, newMask);\n\t\t\t\t\t\twhile (newKeys[ns * 2] !== 0x7FFFFFFF) ns = (ns + 1) & newMask;\n\t\t\t\t\t\tnewKeys[ns * 2] = ocx;\n\t\t\t\t\t\tnewKeys[ns * 2 + 1] = ocy;\n\t\t\t\t\t\tnewCounts[ns] = tempHashCounts[s];\n\t\t\t\t\t}\n\t\t\t\t\ttempHashKeys = newKeys;\n\t\t\t\t\ttempHashCounts = newCounts;\n\n\t\t\t\t\tslot = cellHash(cx, cy, newMask);\n\t\t\t\t\twhile (tempHashKeys[slot * 2] !== cx || tempHashKeys[slot * 2 + 1] !== cy) {\n\t\t\t\t\t\tslot = (slot + 1) & newMask;\n\t\t\t\t\t}\n\t\t\t\t\tpointCellSlot[i] = slot;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (kx === cx && tempHashKeys[slot * 2 + 1] === cy) {\n\t\t\t\ttempHashCounts[slot] += 1;\n\t\t\t\tpointCellSlot[i] = slot;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tslot = (slot + 1) & hashMask;\n\t\t}\n\t}\n\n\t// --- Pass 3: compute offsets via prefix sum ---\n\tconst finalHashMask = (hashCapacity - 1);\n\tconst cellKeys = new Int32Array(cellCount * 2);\n\tconst cellOffsets = new Uint32Array(cellCount);\n\tconst cellLengths = new Uint32Array(cellCount);\n\tconst slotToCellIndex = new Int32Array(hashCapacity);\n\tslotToCellIndex.fill(HASH_EMPTY);\n\n\tlet cellIdx = 0;\n\tlet offset = 0;\n\tfor (let s = 0; s < hashCapacity; s += 1) {\n\t\tif (tempHashKeys[s * 2] === 0x7FFFFFFF) continue;\n\t\tcellKeys[cellIdx * 2] = tempHashKeys[s * 2];\n\t\tcellKeys[cellIdx * 2 + 1] = tempHashKeys[s * 2 + 1];\n\t\tcellOffsets[cellIdx] = offset;\n\t\tcellLengths[cellIdx] = tempHashCounts[s];\n\t\tslotToCellIndex[s] = cellIdx;\n\t\toffset += tempHashCounts[s];\n\t\tcellIdx += 1;\n\t}\n\n\t// --- Pass 4: scatter point indices into flat array ---\n\tconst pointIndices = new Uint32Array(validCount);\n\tconst fillCursor = new Uint32Array(cellCount);\n\tfillCursor.set(cellOffsets);\n\n\tif (drawIndices) {\n\t\tfor (let i = 0; i < validCount; i += 1) {\n\t\t\tconst ci = slotToCellIndex[pointCellSlot[i]];\n\t\t\tpointIndices[fillCursor[ci]] = drawIndices[i];\n\t\t\tfillCursor[ci] += 1;\n\t\t}\n\t} else {\n\t\tlet srcIdx = 0;\n\t\tfor (let i = 0; i < safeCount; i += 1) {\n\t\t\tconst px = positions[i * 2];\n\t\t\tconst py = positions[i * 2 + 1];\n\t\t\tif (!Number.isFinite(px) || !Number.isFinite(py)) continue;\n\t\t\tconst ci = slotToCellIndex[pointCellSlot[srcIdx]];\n\t\t\tpointIndices[fillCursor[ci]] = i;\n\t\t\tfillCursor[ci] += 1;\n\t\t\tsrcIdx += 1;\n\t\t}\n\t}\n\n\t// --- Build final hash table for main-thread lookup ---\n\tlet finalCap = 1;\n\twhile (finalCap < cellCount * 2) finalCap <<= 1;\n\tconst finalMask = finalCap - 1;\n\tconst hashTable = new Int32Array(finalCap);\n\thashTable.fill(HASH_EMPTY);\n\n\tfor (let i = 0; i < cellCount; i += 1) {\n\t\tconst cx = cellKeys[i * 2];\n\t\tconst cy = cellKeys[i * 2 + 1];\n\t\tlet slot = cellHash(cx, cy, finalMask);\n\t\twhile (hashTable[slot] !== HASH_EMPTY) slot = (slot + 1) & finalMask;\n\t\thashTable[slot] = i;\n\t}\n\n\treturn {\n\t\ttype: \"point-hit-index-success\",\n\t\tid: msg.id,\n\t\tcellSize,\n\t\tsafeCount,\n\t\tcellCount,\n\t\thashCapacity: finalCap,\n\t\thashTable: hashTable.buffer,\n\t\tcellKeys: cellKeys.buffer,\n\t\tcellOffsets: cellOffsets.buffer,\n\t\tcellLengths: cellLengths.buffer,\n\t\tpointIndices: pointIndices.buffer,\n\t\tdurationMs: nowMs() - start,\n\t};\n}\n\ninterface WorkerScope {\n\tpostMessage(message: unknown, transfer?: Transferable[]): void;\n\taddEventListener(type: \"message\", listener: (event: MessageEvent<PointHitIndexWorkerRequest>) => void): void;\n}\n\nconst workerScope = self as unknown as WorkerScope;\n\nworkerScope.addEventListener(\"message\", (event: MessageEvent<PointHitIndexWorkerRequest>) => {\n\tconst data = event.data;\n\tif (!data || data.type !== \"point-hit-index-request\") return;\n\n\ttry {\n\t\tconst result = handleRequest(data);\n\t\tif (!result) {\n\t\t\tconst empty: PointHitIndexWorkerSuccess = {\n\t\t\t\ttype: \"point-hit-index-success\",\n\t\t\t\tid: data.id,\n\t\t\t\tcellSize: 0,\n\t\t\t\tsafeCount: 0,\n\t\t\t\tcellCount: 0,\n\t\t\t\thashCapacity: 0,\n\t\t\t\thashTable: new Int32Array(0).buffer,\n\t\t\t\tcellKeys: new Int32Array(0).buffer,\n\t\t\t\tcellOffsets: new Uint32Array(0).buffer,\n\t\t\t\tcellLengths: new Uint32Array(0).buffer,\n\t\t\t\tpointIndices: new Uint32Array(0).buffer,\n\t\t\t\tdurationMs: 0,\n\t\t\t};\n\t\t\tworkerScope.postMessage(empty, [\n\t\t\t\tempty.hashTable, empty.cellKeys,\n\t\t\t\tempty.cellOffsets, empty.cellLengths, empty.pointIndices,\n\t\t\t]);\n\t\t\treturn;\n\t\t}\n\t\tworkerScope.postMessage(result, [\n\t\t\tresult.hashTable, result.cellKeys,\n\t\t\tresult.cellOffsets, result.cellLengths, result.pointIndices,\n\t\t]);\n\t} catch (error) {\n\t\tconst fail: PointHitIndexWorkerResponse = {\n\t\t\ttype: \"point-hit-index-failure\",\n\t\t\tid: data.id,\n\t\t\terror: toErrorMessage(error),\n\t\t};\n\t\tworkerScope.postMessage(fail);\n\t}\n});\n"],"names":["nowMs","toErrorMessage","error","resolveGridSize","sourceWidth","sourceHeight","visibleCount","area","raw","cellHash","cellX","cellY","mask","handleRequest","msg","start","count","positions","maxCountByPositions","safeCount","drawIndices","allValid","i","filtered","cursor","cellSize","invCellSize","pointCellX","pointCellY","validCount","pi","px","py","estimatedCells","hashCapacity","hashMask","tempHashKeys","tempHashCounts","cellCount","pointCellSlot","cx","cy","slot","kx","oldCap","newMask","newKeys","newCounts","s","ocx","ocy","ns","cellKeys","cellOffsets","cellLengths","slotToCellIndex","cellIdx","offset","pointIndices","fillCursor","ci","srcIdx","finalCap","finalMask","hashTable","workerScope","event","data","result","empty","fail"],"mappings":"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"}
|