Skip to content

Commit b5f50d8

Browse files
committed
Bvv: workaround paths with artificial zig-zag pattern
1 parent ab37156 commit b5f50d8

1 file changed

Lines changed: 62 additions & 0 deletions

File tree

  • src/main/java/sc/fiji/snt/viewer

‎src/main/java/sc/fiji/snt/viewer/Bvv.java‎

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,23 @@ public static Bvv getInstance() {
121121
// converted to a world-space distance via pickRadiusWorld() so it stays perceptually constant across zoom levels
122122
private static final double FORK_POINT_PICK_RADIUS_SCREEN_PX = 50;
123123

124+
/* Tracing-click recenter gating (see registerCenterOnDoubleClickListener) */
125+
// Set to false to recenter scene on every tracing click. The gating below is for data still streaming
126+
// from disk/network, where recentering on every click may race against tile loading and producing
127+
// zig-zagged paths (most visible away from the focal plane, where perspective error is largest)
128+
private boolean gatedTracingRecenter = true;
129+
// Fraction of nearClip below which a clicked point is considered "close enough" to the focal plane
130+
// that recentering would add nothing but disruption (an animated transform change plus a settle wait)
131+
// Expressed as a fraction of nearClip (see BvvUtils#computeCamParams)
132+
private static final double RECENTER_SKIP_FRACTION_OF_NEAR_CLIP = 0.3;
133+
// Extra buffer (beyond the recenter animation's own duration) given to BVV's tile streaming to catch
134+
// up with the new focal region before ray-max sampling is trusted again
135+
private static final long RECENTER_SETTLE_BUFFER_MS = 150;
136+
// Wall-clock time (System.currentTimeMillis()) until which trace clicks are deferred because a recenter
137+
// animation is either still running or has just finished (tiles for the new region may still be
138+
// streaming in). 0 (or already elapsed) means the scene is considered settled
139+
private volatile long tracingSettleUntilMs = 0;
140+
124141
private MouseEvent lastPositionEvent;
125142
private double[] lastPosition;
126143

@@ -1542,6 +1559,11 @@ private File getSnapshotFile(final String directory) throws IOException {
15421559
* world point maps to the screen center at viewerZ = 0 (the focal plane).
15431560
* This forces BVV to load tiles at the clicked location, making subsequent
15441561
* findClickRayMaxima() calls accurate.
1562+
* <p>
1563+
* While tracing, this fires on every single click (not just double-clicks), so on
1564+
* data still streaming from disk/network the animated recenter may race against tile
1565+
* loading: the very next click can land mid-animation, against tiles that are still
1566+
* catching up to the *previous* recenter. See {@link #gatedTracingRecenter}
15451567
*/
15461568
private void registerCenterOnDoubleClickListener(final BigVolumeViewer bvv) {
15471569
bvv.getViewer().getDisplay().getComponent().addMouseListener(
@@ -1580,6 +1602,19 @@ public void mouseClicked(final java.awt.event.MouseEvent e) {
15801602
vp.state().getViewerTransform(current);
15811603
final double[] viewerPt = new double[3];
15821604
current.apply(target, viewerPt);
1605+
1606+
if (reactTracingEnabled && gatedTracingRecenter) {
1607+
// Skip the recenter entirely when the click already lands close enough to the
1608+
// focal plane that foreshortening error is negligible. This is the common case
1609+
// once a path settles near the focal plane, and skipping it avoids disrupting
1610+
// fluid tracing on every single click
1611+
final double nearClip = (pathOverlay != null) ? pathOverlay.overlayRenderer.nearClip
1612+
: BvvUtils.DEFAULT_NEAR_CLIP;
1613+
if (Math.abs(viewerPt[2]) < RECENTER_SKIP_FRACTION_OF_NEAR_CLIP * nearClip) {
1614+
return;
1615+
}
1616+
}
1617+
15831618
final double cx = vp.getDisplay().getWidth() / 2.0;
15841619
final double cy = vp.getDisplay().getHeight() / 2.0;
15851620
final AffineTransform3D dest = current.copy();
@@ -1588,6 +1623,23 @@ public void mouseClicked(final java.awt.event.MouseEvent e) {
15881623
dest.set(current.get(2, 3) + (0 - viewerPt[2]), 2, 3);
15891624
// Calling setTransformAnimator replaces any running animation immediately (interruptible)
15901625
vp.setTransformAnimator(new SimilarityTransformAnimator(current, dest, 0, 0, CENTER_ANIMATION_DURATION_MS));
1626+
1627+
if (reactTracingEnabled && gatedTracingRecenter) {
1628+
// Defer trace-click acceptance until the animation finishes and tiles for the
1629+
// new focal region have had a chance to stream in (see Tracer#mouseClicked)
1630+
final long settleDelayMs = CENTER_ANIMATION_DURATION_MS + RECENTER_SETTLE_BUFFER_MS;
1631+
tracingSettleUntilMs = System.currentTimeMillis() + settleDelayMs;
1632+
setTracingStatus(true, "Stabilizing...");
1633+
// Clears the status once the *latest* settle window has elapsed. If another
1634+
// recenter fires before this runs, tracingSettleUntilMs will have been pushed
1635+
// further out and this no-ops, leaving it to the newer timer to clear instead
1636+
final long expectedSettle = tracingSettleUntilMs;
1637+
final javax.swing.Timer settleTimer = new javax.swing.Timer((int) settleDelayMs, ev -> {
1638+
if (tracingSettleUntilMs == expectedSettle) setTracingStatus(false, null);
1639+
});
1640+
settleTimer.setRepeats(false);
1641+
settleTimer.start();
1642+
}
15911643
}
15921644
});
15931645
}
@@ -3202,6 +3254,16 @@ public void mouseClicked(final MouseEvent e) {
32023254
}
32033255
if (computing) return; // a segment is still being traced; ignore further clicks until it lands
32043256

3257+
if (gatedTracingRecenter && System.currentTimeMillis() < tracingSettleUntilMs) {
3258+
// A recenter animation triggered by a previous click is still running, or has just
3259+
// finished but tiles for the new focal region may still be streaming in (see
3260+
// registerCenterOnDoubleClickListener). Sampling now risks the exact zig-zag this gate
3261+
// exists to prevent, so the click is dropped rather than silently placing a bad node;
3262+
// the status row (set by the recenter listener) already reads "Stabilizing..."
3263+
showViewerMessage("Scene still stabilizing, please click again");
3264+
return;
3265+
}
3266+
32053267
if (previousNode == null) {
32063268
// this is the first click: we are starting a new Path. Lock in the channel/frame (and, for A*,
32073269
// the pixel data) for the whole path now, rather than re-checking on every segment, so a single

0 commit comments

Comments
 (0)