feat(globe): a follow handle names its target

FollowHandle.target() returns the followed point, checked by the contract
on both engines. The contract's model check now waits for ready() before
looking for the model's pixels: the magenta point and sprites could fill
the region before the glTF loaded (qa-globe-contract cesium 73/73,
navara 79/79).
This commit is contained in:
German Khokhlov committed 2026-10-17 03:48:11 +04:00
1 parent ba1191b2e5
commit dcfb1219d5
4 files changed
+26 -1

No files matched your search

+9
View File
@@ -188,6 +188,8 @@ export function createCesiumFollow(viewer, { commanded }) {
if (!handle) { if (!handle) {
handle = { handle = {
native: entity, native: entity,
target: () =>
displayPosition(entity, viewer.clock.currentTime, undefined),
stop() { stop() {
if (viewer.trackedEntity === entity) viewer.trackedEntity = undefined; if (viewer.trackedEntity === entity) viewer.trackedEntity = undefined;
}, },
@@ -306,6 +308,13 @@ export function createCesiumFollow(viewer, { commanded }) {
const state = { entity: null, proxy: null, options, handle: null }; const state = { entity: null, proxy: null, options, handle: null };
state.handle = { state.handle = {
native: options.native, native: options.native,
target() {
try {
return state.options.position();
} catch {
return undefined;
}
},
stop() { stop() {
if (own !== state) return; if (own !== state) return;
if (viewer.trackedEntity === state.entity) if (viewer.trackedEntity === state.entity)
+2
View File
@@ -150,6 +150,8 @@
* @typedef {object} FollowHandle * @typedef {object} FollowHandle
* @property {() => void} stop * @property {() => void} stop
* @property {(next: Partial<FollowOptions>) => void} retarget * @property {(next: Partial<FollowOptions>) => void} retarget
* @property {() => (Cartesian3 | undefined)} target Where the followed
* target is now (what the follow centres).
* @property {any} [native] * @property {any} [native]
* @property {object} [label] FollowOptions.label. * @property {object} [label] FollowOptions.label.
* *
+7
View File
@@ -350,6 +350,13 @@ export function createFollowController({
label: options.label, label: options.label,
offset: null, offset: null,
previous: null, previous: null,
target() {
try {
return handle.options.position();
} catch {
return undefined;
}
},
stop: () => end(handle), stop: () => end(handle),
retarget(next) { retarget(next) {
const updated = normalizeFollowOptions({ ...options, ...next }); const updated = normalizeFollowOptions({ ...options, ...next });
+8 -1
View File
@@ -1209,8 +1209,10 @@ export function createContractSuite(
count++; count++;
return count; return count;
}; };
// ready() turns true once the glTF has loaded (the magenta point
// and sprites above can already fill the region before then).
await until(() => models.ready(), 'the model set ready()');
await until(() => modelPixels() > 150, 'the model drawn'); await until(() => modelPixels() > 150, 'the model drawn');
ensure(models.ready(), 'a drawn model set is not ready()');
} finally { } finally {
for (const set of sets) set.destroy(); for (const set of sets) set.destroy();
} }
@@ -2572,6 +2574,11 @@ export function createContractSuite(
); );
await until(() => offCentre() <= 3, 'the follow centring its target'); await until(() => offCentre() <= 3, 'the follow centring its target');
near(range(), Math.hypot(1000, 400), 5, 'follow range at viewFrom'); near(range(), Math.hypot(1000, 400), 5, 'follow range at viewFrom');
// target(): where the followed target is now.
ensure(
cartesian3.distance(handle.target(), at(step)) < 1,
'following().target() is not the target',
);
// 2 s of motion (at least 120 frames), centred within 3 px. // 2 s of motion (at least 120 frames), centred within 3 px.
const began = performance.now(); const began = performance.now();
const firstStep = step; const firstStep = step;