mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-12 14:22:21 +00:00
Pivot a tilt at exactly 1,000 km and report where a zoom now ends it
A pivot range rebuilt from the camera's position came back a hair over the 1,000 km cutoff when the view sat exactly on it, so a tilt from 1,000 km ran in place and lifted the crosshair onto the sky. The cutoff now allows 1 m of slack. A zoom during a tilt to an angle changes the range the pivot holds, and with it the place's horizon limit, but the tilt kept its old goal and its reply its old angle: from 400 km, a tilt to -5° zoomed out x2.5 reported -5° with no note and ended at -9.4°. The zoom now re-judges the goal at the new range, eases the tilt from where it is to it, and its result names the angle the tilt now ends at, with a note.
This commit is contained in:
1 parent
dae95e1c7f
commit
6479167b50
4 files changed
+139
-8
No files matched your search
@@ -610,3 +610,79 @@ for (const direction of ['left', 'right'])
|
||||
assert.match(angled.error, /pitchDeg alone/);
|
||||
assert.equal(getActiveCameraMotion(), null);
|
||||
});
|
||||
|
||||
// The 1,000 km pivot cutoff includes its boundary: at exactly 1,000 km the
|
||||
// pivot range came back a hair over it, so the tilt ran in place and put the
|
||||
// crosshair on the sky.
|
||||
test('from exactly 1,000 km a tilt to -5° still pivots about the place', (t) => {
|
||||
const h = harness(t, { height: 1_000_000 });
|
||||
const result = moveCamera({ motion: 'tilt', pitchDeg: 5 });
|
||||
assert.equal(result.ok, true);
|
||||
h.finish();
|
||||
assert.ok(h.viewTarget(), 'the crosshair is on the sky');
|
||||
assert.ok(centreDrift(h) < 100, `centre drifted ${centreDrift(h)} m`);
|
||||
assert.ok(
|
||||
Math.abs(pitchDeg(h.camera) - result.pitchDeg) < 0.5,
|
||||
`camera at ${pitchDeg(h.camera)}°, reported ${result.pitchDeg}°`,
|
||||
);
|
||||
assert.match(result.note, /horizon/);
|
||||
});
|
||||
|
||||
// A zoom mid-tilt moves the pivot's horizon limit: the zoom's reply names the
|
||||
// angle the tilt now ends at, and the camera ends there.
|
||||
test('a zoom out during a tilt to an angle reports the angle it now ends at', (t) => {
|
||||
const h = harness(t, { height: 400_000 });
|
||||
const result = moveCamera({ motion: 'tilt', pitchDeg: -5 });
|
||||
assert.equal(result.pitchDeg, -5);
|
||||
assert.equal(result.note, undefined);
|
||||
for (let i = 0; i < 10; i += 1) h.tick();
|
||||
const zoom = adjustOrbitRange(2.5);
|
||||
assert.equal(zoom.kind, 'tilt');
|
||||
assert.equal(zoom.changed, true);
|
||||
h.finish();
|
||||
assert.ok(h.viewTarget(), 'the crosshair is on the sky');
|
||||
assert.ok(zoom.pitchDeg < -5, `zoom reported ${zoom.pitchDeg}°`);
|
||||
assert.ok(
|
||||
Math.abs(pitchDeg(h.camera) - zoom.pitchDeg) < 0.5,
|
||||
`camera at ${pitchDeg(h.camera)}°, zoom reported ${zoom.pitchDeg}°`,
|
||||
);
|
||||
assert.match(zoom.note, /horizon/);
|
||||
});
|
||||
|
||||
test('a zoom in during a horizon-limited tilt reports the angle it now reaches', (t) => {
|
||||
const h = harness(t, { height: 900_000 });
|
||||
const result = moveCamera({ motion: 'tilt', pitchDeg: -5 });
|
||||
assert.ok(result.pitchDeg < -5, `reported ${result.pitchDeg}°`);
|
||||
for (let i = 0; i < 10; i += 1) h.tick();
|
||||
const zoom = adjustOrbitRange(1 / 2.4);
|
||||
assert.equal(zoom.changed, true);
|
||||
h.finish();
|
||||
assert.equal(zoom.pitchDeg, -5);
|
||||
assert.ok(
|
||||
Math.abs(pitchDeg(h.camera) - -5) < 0.5,
|
||||
`camera at ${pitchDeg(h.camera)}°`,
|
||||
);
|
||||
});
|
||||
|
||||
test('a voice zoom during a tilt to an angle passes on the new end angle', async (t) => {
|
||||
const h = harness(t, { height: 400_000 });
|
||||
const runner = createGevActionRunner({
|
||||
viewer: h.viewer,
|
||||
styleManager: {},
|
||||
dataManager: { layers: new Map(), getAll: () => [] },
|
||||
});
|
||||
moveCamera({ motion: 'tilt', pitchDeg: -5 });
|
||||
for (let i = 0; i < 10; i += 1) h.tick();
|
||||
const zoom = await runner('adjust_camera_zoom', {
|
||||
direction: 'out',
|
||||
amount: 'lot',
|
||||
});
|
||||
assert.equal(zoom.ok, true);
|
||||
assert.equal(zoom.tiltRangeAdjusted, true);
|
||||
h.finish();
|
||||
assert.ok(
|
||||
Math.abs(pitchDeg(h.camera) - zoom.pitchDeg) < 0.5,
|
||||
`camera at ${pitchDeg(h.camera)}°, zoom reported ${zoom.pitchDeg}°`,
|
||||
);
|
||||
assert.match(zoom.note, /horizon/);
|
||||
});
|
||||
+57
-7
@@ -62,6 +62,12 @@ const TILT_MIN_CLEARANCE_M = 150;
|
||||
* crosshair point, so the tilt happens in place instead.
|
||||
*/
|
||||
const TILT_PIVOT_MAX_RANGE_M = 1_000_000;
|
||||
/**
|
||||
* A pivot range rebuilt from the camera's position comes back a hair off the
|
||||
* range it was set at: a range this close past the cutoff still counts as on
|
||||
* it.
|
||||
*/
|
||||
const TILT_PIVOT_RANGE_SLACK_M = 1;
|
||||
/**
|
||||
* The shallowest a pivot swing goes when its clearance is not what stops it.
|
||||
* From several hundred km the camera's own horizon limit lies past the
|
||||
@@ -1403,10 +1409,20 @@ function tiltUpLimit({ target, pivot }) {
|
||||
function tiltStartFrame(cam) {
|
||||
const target = _viewer.trackedEntity ? null : _getTarget?.(_viewer);
|
||||
let pivot = target ? tiltPivot(cam, target) : null;
|
||||
if (pivot && pivot.range > TILT_PIVOT_MAX_RANGE_M) pivot = null;
|
||||
if (pivot && !tiltPivotInReach(pivot.range)) pivot = null;
|
||||
return { target, pivot, from: cam.pitch };
|
||||
}
|
||||
|
||||
/** Whether a tilt may pivot at this range: up to and including the cutoff. */
|
||||
function tiltPivotInReach(range) {
|
||||
return range <= TILT_PIVOT_MAX_RANGE_M + TILT_PIVOT_RANGE_SLACK_M;
|
||||
}
|
||||
|
||||
/** The reply's note for a tilt the place's horizon stops short of its angle. */
|
||||
function tiltHorizonNote(pitch) {
|
||||
return `Stopped at ${Math.round(Cesium.Math.toDegrees(pitch))}°: from this range a shallower angle puts the place past the horizon. Zoom in to tilt further.`;
|
||||
}
|
||||
|
||||
/** Ground pivot for a tilt: heading, pitch and range in the target's own frame. */
|
||||
function tiltPivot(cam, target) {
|
||||
const local = Cesium.Matrix4.multiplyByPoint(
|
||||
@@ -1917,6 +1933,9 @@ export function moveCamera(args = {}, runNavigation = null) {
|
||||
state.mode = 'once';
|
||||
state.direction = goal > from ? 'up' : 'down';
|
||||
state.goalPitch = goal;
|
||||
// A zoom mid-tilt moves the horizon limit; it re-judges the goal
|
||||
// against the angle asked for.
|
||||
state.askedPitch = tiltToPitch;
|
||||
state.durationS = Math.max(
|
||||
ONCE.durationS,
|
||||
Math.abs(Cesium.Math.toDegrees(goal - from)) / TILT_TO_DEG_S,
|
||||
@@ -2024,9 +2043,7 @@ export function moveCamera(args = {}, runNavigation = null) {
|
||||
Cesium.Math.toDegrees(horizonLimited ?? tiltToPitch),
|
||||
),
|
||||
}),
|
||||
...(horizonLimited !== null && {
|
||||
note: `Stopped at ${Math.round(Cesium.Math.toDegrees(horizonLimited))}°: from this range a shallower angle puts the place past the horizon. Zoom in to tilt further.`,
|
||||
}),
|
||||
...(horizonLimited !== null && { note: tiltHorizonNote(horizonLimited) }),
|
||||
};
|
||||
};
|
||||
const preserveCameraFlight =
|
||||
@@ -2043,8 +2060,10 @@ export function moveCamera(args = {}, runNavigation = null) {
|
||||
* fight the per-frame lookAt (field test: "zoom out did literally nothing").
|
||||
* A tilt swinging about its pivot holds a range the same way.
|
||||
* @param {number} factor >1 zooms out (radius grows), <1 zooms in.
|
||||
* @returns {{kind: string, changed: boolean}|null} the motion that consumed
|
||||
* the zoom, and whether its range moved; null when none did.
|
||||
* @returns {{kind: string, changed: boolean, pitchDeg?: number,
|
||||
* note?: string}|null} the motion that consumed the zoom, and whether its
|
||||
* range moved; null when none did. A tilt to an angle whose end the new
|
||||
* range moves also names the angle it now ends at (pitchDeg), with a note.
|
||||
*/
|
||||
export function adjustOrbitRange(factor) {
|
||||
if (!_active || !['orbit', 'tilt'].includes(_active.kind) || !_active.hpr)
|
||||
@@ -2066,7 +2085,38 @@ export function adjustOrbitRange(factor) {
|
||||
Math.min(Math.max(ceiling, range), range * factor),
|
||||
);
|
||||
// Under half a metre counts as not moved, as for a plain zoom.
|
||||
return { kind, changed: Math.abs(hpr.range - range) >= 0.5 };
|
||||
const changed = Math.abs(hpr.range - range) >= 0.5;
|
||||
return { kind, changed, ...(changed && retargetTiltAfterZoom(_active)) };
|
||||
}
|
||||
|
||||
/**
|
||||
* A zoom changed the range a tilt to an angle holds, and with it the place's
|
||||
* horizon limit: aim the tilt at the angle asked for or the new limit,
|
||||
* whichever comes first, from where the camera is now. Returns the end angle
|
||||
* and a note for the reply when that end moved, otherwise nothing.
|
||||
*/
|
||||
function retargetTiltAfterZoom(m) {
|
||||
if (m.kind !== 'tilt' || m.mode !== 'once' || m.askedPitch === undefined)
|
||||
return {};
|
||||
const from = _viewer.camera.pitch;
|
||||
if (!(m.askedPitch > from)) return {};
|
||||
const limit = tiltUpLimit({ target: m.target, pivot: m.hpr });
|
||||
const goal = Math.min(m.askedPitch, Math.max(from, limit));
|
||||
if (Math.abs(goal - m.goalPitch) < 1e-6) return {};
|
||||
m.goalPitch = goal;
|
||||
m.startPitch = from;
|
||||
m.elapsed = 0;
|
||||
m.durationS = Math.max(
|
||||
ONCE.durationS,
|
||||
Math.abs(Cesium.Math.toDegrees(goal - from)) / TILT_TO_DEG_S,
|
||||
);
|
||||
const limited = goal !== m.askedPitch;
|
||||
return {
|
||||
pitchDeg: Math.round(Cesium.Math.toDegrees(goal)),
|
||||
note: limited
|
||||
? tiltHorizonNote(goal)
|
||||
: `The tilt now reaches ${Math.round(Cesium.Math.toDegrees(goal))}°.`,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -471,6 +471,11 @@ export function createGevActionRunner({
|
||||
amount: amt,
|
||||
[held.kind === 'tilt' ? 'tiltRangeAdjusted' : 'orbitRadiusAdjusted']:
|
||||
true,
|
||||
// A tilt to an angle names the angle the new range now ends it at.
|
||||
...(held.pitchDeg !== undefined && {
|
||||
pitchDeg: held.pitchDeg,
|
||||
note: held.note,
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user