+ {loading ? (
+
+
+
+ ) : null}
+ {!loading && loadBulkAnnotations ? (
+
+ ) : null}
+
+ {
+ if (
+ vs &&
+ typeof vs === 'object' &&
+ 'zoom' in vs &&
+ 'target' in vs
+ ) {
+ const sr = slideRef.current
+ if (!sr) {
+ return
+ }
+ const { width: cw, height: ch } = sizeRef.current
+ const lim = orthographicZoomLimits(
+ cw,
+ ch,
+ sr.worldW,
+ sr.worldH,
+ sr.levelCount,
+ )
+ const rawZ = vs.zoom as number
+ const zClamped = Math.min(
+ lim.maxZoom,
+ Math.max(lim.minZoom, rawZ),
+ )
+ const zq = Number(zClamped.toFixed(5))
+ const t = vs.target as [number, number] | [number, number, number]
+ setViewState({
+ target: [t[0], t[1], t[2] ?? 0],
+ zoom: zq,
+ })
+ }
+ }}
+ controller={{ inertia: false }}
+ layers={layers}
+ width={size.width}
+ height={size.height}
+ getCursor={({ isHovering }) => (isHovering ? 'pointer' : 'grab')}
+ onHover={handleBulkDeckHover}
+ />
+
+ {bulkHoverTooltip != null ? (
+
+ ) : null}
+
+ )
+}
+
+export default VivSlideViewport
diff --git a/src/viv/centerOutAnnotationOrder.ts b/src/viv/centerOutAnnotationOrder.ts
new file mode 100644
index 00000000..5beb57e1
--- /dev/null
+++ b/src/viv/centerOutAnnotationOrder.ts
@@ -0,0 +1,190 @@
+/** Minimum annotation count before offloading center-out sort to a Web Worker. */
+export const CENTER_OUT_ORDER_WORKER_MIN = 25_000
+
+export type CenterOutOrderInput = {
+ numberOfAnnotations: number
+ graphicData: Int32Array | Float32Array
+ graphicIndex: Int32Array | null
+ coordinateDimensionality: number
+ commonZCoordinate: number
+ deckCoeffs: readonly [number, number, number, number, number, number]
+ loadCenter: [number, number]
+}
+
+function openLayersMapYToVivWorldY(mapY: number): number {
+ return -mapY - 1
+}
+
+function bulkVertexToDeckFast(
+ gx: number,
+ gy: number,
+ c: readonly [number, number, number, number, number, number],
+): [number, number] {
+ const pcol = c[0] * gx + c[1] * gy + c[2]
+ const prow = c[3] * gx + c[4] * gy + c[5]
+ const olMapY = -(prow + 1)
+ return [pcol, openLayersMapYToVivWorldY(olMapY)]
+}
+
+function readTripleFromGraphicBuffer(
+ graphicData: Int32Array | Float32Array,
+ j: number,
+ commonZCoordinate: number,
+): [number, number, number] {
+ const gx = Number(graphicData[j])
+ const gy = Number(graphicData[j + 1])
+ const gz = Number.isNaN(commonZCoordinate)
+ ? Number(graphicData[j + 2])
+ : Number(commonZCoordinate)
+ return [gx, gy, gz]
+}
+
+/** Main-thread center-out sort (used for smaller groups and as worker fallback). */
+export function computeCenterOutAnnotationOrderSync(
+ options: CenterOutOrderInput,
+): Uint32Array {
+ const {
+ numberOfAnnotations,
+ graphicData,
+ graphicIndex,
+ coordinateDimensionality,
+ commonZCoordinate,
+ deckCoeffs,
+ loadCenter,
+ } = options
+ const [cx, cy] = loadCenter
+ const distances = new Float64Array(numberOfAnnotations)
+ const hasIndex = graphicIndex !== null && graphicIndex !== undefined
+ const minRemain = coordinateDimensionality >= 3 ? 3 : 2
+
+ for (let i = 0; i < numberOfAnnotations; i++) {
+ distances[i] = Number.POSITIVE_INFINITY
+ const offset = hasIndex
+ ? Number(graphicIndex[i] ?? 0) - 1
+ : i * coordinateDimensionality
+ if (offset < 0 || offset + minRemain - 1 >= graphicData.length) {
+ continue
+ }
+ const [gx, gy] = readTripleFromGraphicBuffer(
+ graphicData,
+ offset,
+ commonZCoordinate,
+ )
+ if (!gx || !gy) {
+ continue
+ }
+ const [dx, dy] = bulkVertexToDeckFast(gx, gy, deckCoeffs)
+ const ddx = dx - cx
+ const ddy = dy - cy
+ distances[i] = ddx * ddx + ddy * ddy
+ }
+
+ const order = new Uint32Array(numberOfAnnotations)
+ for (let i = 0; i < numberOfAnnotations; i++) {
+ order[i] = i
+ }
+ order.sort((a, b) => distances[a] - distances[b])
+ return order
+}
+
+let centerOutWorker: Worker | null = null
+let centerOutWorkerSeq = 0
+
+function getCenterOutWorker(): Worker {
+ if (centerOutWorker != null) {
+ return centerOutWorker
+ }
+ const blob = new Blob(
+ [
+ `self.onmessage=function(e){var d=e.data,id=d.id,p=d.payload,b=d.buffers;var gd=new Float64Array(b.graphicData);var gi=b.graphicIndex?new Int32Array(b.graphicIndex):null;var n=p.numberOfAnnotations,cx=p.loadCenter[0],cy=p.loadCenter[1],cd=p.coordinateDimensionality,cz=p.commonZCoordinate,c=p.deckCoeffs,minR=cd>=3?3:2,dist=new Float64Array(n);for(var i=0;i