feat(annotations): draw on the world by hand (#547)

Supersedes #235 by @cora-fresh-labs, ported onto the current component layout with fixes from review.

Verified before merge: format, package boundaries, unit suite, build, and the browser tracking suite; a browser harness for the change where one applies; adversarial review applied.

Future work (not in this PR): hand-drawn marks do not persist across a reload today, and neither do spoken annotations; there is no storage layer for marks. The natural home is Scenes: a drawing that belongs to a shot and persists per shot, so a scene animation can carry its own marks. Needs an owner.
Co-authored-by: Clint Borle <[email protected]>
This commit is contained in:
Bilawal Sidhu authored and GitHub committed 2026-09-14 19:59:26 -05:00
1 parent 409e27d7f9
commit 9fc8d1966b
32 files changed
+3313 -19

No files matched your search

+3
View File
@@ -1,4 +1,5 @@
import * as Cesium from 'cesium';
import { isPointerFree } from '../../data/inputOwnership.js';
import {
LAYER_ID,
MAX_VIEWPORT_DEGREES,
@@ -376,6 +377,8 @@ export function createAlprPresentation({ state, services, source }) {
viewer.scene.canvas,
);
state.clickHandler.setInputAction((click) => {
// A tool owns the pointer (src/data/inputOwnership.js): yield the click.
if (!isPointerFree()) return;
if (!state.enabled) return;
const picked = viewer.scene.pick(click.position);
const id = typeof picked?.id?.id === 'string' ? picked.id.id : null;
+3
View File
@@ -1,4 +1,5 @@
import * as Cesium from 'cesium';
import { isPointerFree } from '../../data/inputOwnership.js';
import {
BIKESHARE_SELECTED_OVERLAY_SOURCE_ID,
BIKESHARE_SELECTED_OVERLAY_SOURCE_OPTIONS,
@@ -153,6 +154,8 @@ export function createSelection({
viewer.scene.canvas,
);
layerState._clickHandler.setInputAction((click) => {
// A tool owns the pointer (src/data/inputOwnership.js): yield the click.
if (!isPointerFree()) return;
const picked = viewer.scene.pick(click.position);
if (picked) {
+3
View File
@@ -3,6 +3,7 @@ import {
fireDetectionKey,
} from '../../data/firmsLabels.js';
import * as Cesium from 'cesium';
import { isPointerFree } from '../../data/inputOwnership.js';
import { CONTEXT_TOP_N } from './policy.js';
export function createSelection({
@@ -35,6 +36,8 @@ export function createSelection({
layerState._viewer,
);
layerState._clickHandler.setInputAction((click) => {
// A tool owns the pointer (src/data/inputOwnership.js): yield the click.
if (!isPointerFree()) return;
const picked = layerState._viewer.scene.pick(click.position);
const fire = pickedFire(picked);
if (fire) {
+3
View File
@@ -1,4 +1,5 @@
import * as Cesium from 'cesium';
import { isPointerFree } from '../../data/inputOwnership.js';
import { LAYER_ID } from './policy.js';
export function createSelection({
@@ -24,6 +25,8 @@ export function createSelection({
viewer.scene.canvas,
);
layerState.clickHandler.setInputAction((click) => {
// A tool owns the pointer (src/data/inputOwnership.js): yield the click.
if (!isPointerFree()) return;
if (!layerState.enabled) return;
const picked = viewer.scene.pick(click.position);
const id = typeof picked?.id?.id === 'string' ? picked.id.id : null;
+3
View File
@@ -1,4 +1,5 @@
import * as Cesium from 'cesium';
import { isPointerFree } from '../../data/inputOwnership.js';
export function createLifecycle({
state: layerState,
@@ -26,6 +27,8 @@ export function createLifecycle({
viewer.scene.canvas,
);
layerState._clickHandler.setInputAction((movement) => {
// A tool owns the pointer (src/data/inputOwnership.js): yield the click.
if (!isPointerFree()) return;
if (!layerState._enabled || !layerState._dataSource?.show) return;
const entity = viewer.scene
.drillPick(movement.position, 12)
+3
View File
@@ -1,4 +1,5 @@
import * as Cesium from 'cesium';
import { isPointerFree } from '../../data/inputOwnership.js';
import {
RADIO_PREFIX,
RADIO_PICK_OFFSETS,
@@ -117,6 +118,8 @@ export function createInteraction({
layerState._viewer.scene.canvas,
);
layerState._clickHandler.setInputAction((click) => {
// A tool owns the pointer (src/data/inputOwnership.js): yield the click.
if (!isPointerFree()) return;
if (!radioPresentationAllowed()) return;
const stationId = pickedRadioStationAt(click.position);
if (!stationId) return;
+3
View File
@@ -1,4 +1,5 @@
import * as Cesium from 'cesium';
import { isPointerFree } from '../../data/inputOwnership.js';
export function createInteraction({
state: layerState,
@@ -46,6 +47,8 @@ export function createInteraction({
viewer.scene.canvas,
);
layerState._clickHandler.setInputAction((click) => {
// A tool owns the pointer (src/data/inputOwnership.js): yield the click.
if (!isPointerFree()) return;
if (!layerState._enabled) return;
const picked = viewer.scene.pick(click.position);
@@ -1,4 +1,5 @@
import * as Cesium from 'cesium';
import { isPointerFree } from '../../data/inputOwnership.js';
export function createInteraction({ state, screenSpaceEventHandlerFactory }) {
function registerPickEntity(entity, info) {
@@ -10,6 +11,8 @@ export function createInteraction({ state, screenSpaceEventHandlerFactory }) {
if (state._clickHandler) return;
state._clickHandler = screenSpaceEventHandlerFactory(viewer.scene.canvas);
state._clickHandler.setInputAction((click) => {
// A tool owns the pointer (src/data/inputOwnership.js): yield the click.
if (!isPointerFree()) return;
if (!state._enabled) return;
const picked = viewer.scene.pick(click.position);
const record = resolvePickRecord(picked);
+3
View File
@@ -1,4 +1,5 @@
import * as Cesium from 'cesium';
import { isPointerFree } from '../../data/inputOwnership.js';
import { VESSEL_OVERLAY_SOURCE_ID } from '../../data/vesselLabels.js';
export function createSelection({
@@ -33,6 +34,8 @@ export function createSelection({
function bindVesselInteraction(viewer, handler, keyTarget) {
state.clickHandler = handler;
handler.setInputAction((click) => {
// A tool owns the pointer (src/data/inputOwnership.js): yield the click.
if (!isPointerFree()) return;
if (!state.feed.enabled) return;
const picked = viewer.scene.pick(click.position);
const pickedId = resolvePickId(picked);