Skip to content

Add reorientation component: face the direction of travel#640

Open
borismasis wants to merge 1 commit into
mapillary:mainfrom
borismasis:reorientation
Open

Add reorientation component: face the direction of travel#640
borismasis wants to merge 1 commit into
mapillary:mainfrom
borismasis:reorientation

Conversation

@borismasis

@borismasis borismasis commented Jun 24, 2026

Copy link
Copy Markdown
Member

Add reorientation component: face the direction of travel

Summary

Adds a reorientation component that, as the user navigates a sequence,
reorients each spherical image to face the direction of travel (the
great-circle bearing toward the next image) instead of preserving the previous
look direction.

Active by default, opt-out like other default components:

const viewer = new Viewer({
    accessToken: "<token>",
    container: "<id>",
    component: { reorientation: false }, // disable
});

Why

When stepping through a sequence the stock viewer keeps the previous look
direction, so a user looking forward ends up looking sideways/backward after a
turn. Facing the travel direction matches user intent for "driving down the
street" navigation. Because it lives in mapillary-js, every client that embeds
the viewer benefits.

How

  • ReorientationEngine (pure, unit-tested): from an image's position, the
    next image's position, and the compass angle, computes the basic-x that frames
    the travel direction. Motion is detected from GPS speed with a low-speed-turn
    allowance; GPS outliers are rejected against recent bearings; stationary images
    fall back to the last confirmed moving bearing so the view is preserved rather
    than spun by noise. Results are cached and prefetched ahead so a step lands on
    an already-resolved bearing.

  • ReorientationComponent: subscribes to currentImage$, feeds the engine
    from the viewer's graph (cacheImage$ / cacheSequence$), and steers the view:

    • Snap vs. ease by SfM connectivity. An image with mesh structure (an SfM
      reconstruction) eases to the travel direction via rotateToBasicSmooth;
      a mesh-less image (no smooth transition is possible) snaps via
      rotateToBasic, so the reorientation doesn't pan over what is already an
      instant cut. The decision is per-image, independent of how you arrived
      (deep link, in-sequence step, or jump).
    • Minimum-angle threshold. A reorientation that would move the current view
      less than ~15° is skipped, so near-straight steps don't jitter; it re-faces
      only on real turns.
    • Per-sequence state. A manual look-around offset (see below) and the
      carried pitch are reset on a sequence change — a new sequence starts at the
      travel direction, nothing carries over from the previous one.
    • Manual offset preserved. If the user drags to look around
      (mouseDragEnd$), the horizontal offset from the computed forward is
      remembered and re-applied to subsequent images in the same sequence,
      instead of re-facing forward on every step.
  • Eased state operation rotateToBasicSmooth([basicX, y]) (the eased sibling
    of the existing snap rotateToBasic): sets the state's _desiredLookat, which
    _updateLookat lerps the current camera toward each frame — so an eased
    reorientation stays smooth whether it lands within the running image transition
    or after the transition has already settled.

  • Flash-free hard cuts (state hint). On a motionless/instant transition the
    new image would render one frame at the carried view before the component's
    reactive snap lands, a visible flash. To avoid it, the component registers a
    per-image reorientation hint with the state (setReorientation); the
    traversing state applies it to the image's camera as it becomes current —
    before that frame is rendered — but only on instant transitions (a smooth
    transition still eases). Hints are cleared on a sequence change.

Files

New:

  • src/component/reorientation/ReorientationEngine.ts — pure, testable math +
    motion engine
  • src/component/reorientation/ReorientationComponent.ts
  • src/component/interfaces/ReorientationConfiguration.ts
  • test/component/reorientation/ReorientationEngine.test.ts

Eased state operation (rotateToBasicSmooth, internal service API):

  • src/state/state/InteractiveStateBase.ts (implementation)
  • src/state/state/StateBase.ts (noop default)
  • src/state/StateContext.ts, src/state/interfaces/IStateContext.ts,
    src/state/StateService.ts

Per-image reorientation hint (flash-free hard cuts):

  • src/state/interfaces/IStateBase.ts (reorientations map, shared across state
    transitions)
  • src/state/state/StateBase.ts (setReorientation / clearReorientations)
  • src/state/state/TraversingState.ts (apply the hint when an image becomes
    current on a motionless transition, before render)
  • src/state/StateContext.ts, src/state/interfaces/IStateContext.ts,
    src/state/StateService.ts (plumbing)

Wiring (default-on, opt-out + public export + registration):

  • src/component/ComponentName.ts
  • src/viewer/options/ComponentOptions.ts
  • src/viewer/ComponentController.ts (_uTrue(options.reorientation, ...))
  • src/mapillary.ts (register)
  • src/external/component.ts (export component + config)

Test plan

  • yarn test — full suite green: 95 suites, 1357 tests pass (includes new
    ReorientationEngine unit tests: bearing/haversine/basic-x + wrap-delta math,
    moving detection, low-speed-turn, end-of-sequence + non-spherical
    invalidation, cache reuse; and the ComponentController test updated for the
    new default component).
  • yarn lint clean; yarn build clean (tsc + rollup).
  • Manual: load a sequence in the demo and step through. SfM sequences ease to the
    travel direction (smoothly, including on cold jumps to an unprefetched image);
    mesh-less sequences hard-cut without a flash; near-straight steps hold steady
    and re-face only on turns; dragging to look around is preserved across the
    sequence and reset between sequences. Validated against production imagery in a
    downstream web client running the same engine + steering across ~30 cities.

@meta-cla meta-cla Bot added the cla signed label Jun 24, 2026
Reorients each spherical (360) image to face the direction of travel (the
great-circle bearing toward the next image in the sequence) as the user
navigates, instead of facing off to the side or behind. Can be disabled with component: { reorientation: false }.

ReorientationEngine (pure, unit-tested): GPS-speed motion detection with a
  low-speed-turn allowance, GPS-outlier rejection, and last-confirmed-bearing
  fallback. Results cached and prefetched ahead.
ReorientationComponent: feeds the engine from the viewer's graph and steers
  via a new eased state op (rotateToBasicSmooth); a manual look-around offset
  is preserved across the sequence.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant