mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 19:04:02 +00:00
refactor(director): separate timing clocks and scene pack presentation
This commit is contained in:
1 parent
5728dec24d
commit
8a3c4868b0
19 files changed
+1190
-471
No files matched your search
@@ -0,0 +1,171 @@
|
||||
const DEFAULT_SHOT_DURATION_SEC = 4;
|
||||
const clamp01 = (value) => Math.max(0, Math.min(1, value));
|
||||
const deepClone = (value) => JSON.parse(JSON.stringify(value));
|
||||
|
||||
/** Prepare scene-owned layer state, timing context and seek/travel controls. */
|
||||
export function layerStatesForShot(
|
||||
scene,
|
||||
shot,
|
||||
packs,
|
||||
{ cameraSettled = false, cameraTravel = null, sceneSeek = null } = {},
|
||||
) {
|
||||
const states = deepClone(shot?.layers || {});
|
||||
const shotIndex = scene?.shots?.findIndex(({ id }) => id === shot?.id) ?? -1;
|
||||
const sceneShotDurations =
|
||||
scene?.shots?.map((sceneShot) =>
|
||||
shotRuntimeDurationSec(scene, sceneShot, packs),
|
||||
) || [];
|
||||
const sceneDurationSec = sceneShotDurations.reduce(
|
||||
(sum, duration) => sum + duration,
|
||||
0,
|
||||
);
|
||||
const authoredSceneElapsedSec = sceneShotDurations
|
||||
.slice(0, Math.max(0, shotIndex))
|
||||
.reduce((sum, duration) => sum + duration, 0);
|
||||
const sceneElapsedSec = sceneSeek
|
||||
? Math.max(
|
||||
0,
|
||||
Math.min(sceneDurationSec, Number(sceneSeek.sceneElapsedSec) || 0),
|
||||
)
|
||||
: authoredSceneElapsedSec;
|
||||
const sourceRecipe = packs.recipeForShot(shot);
|
||||
for (const state of Object.values(states)) {
|
||||
if (state?.params?.presentation !== 'scene-beat') continue;
|
||||
state.params.sceneSurface ||= shot.sourcePackId
|
||||
? 'evidence-beat'
|
||||
: 'panel-only';
|
||||
const runtimeControls =
|
||||
sourceRecipe?.runtimeControlsByBeat?.[state.params.beatId];
|
||||
if (runtimeControls) {
|
||||
state.params.sceneControls = {
|
||||
...state.params.sceneControls,
|
||||
...runtimeControls,
|
||||
...(runtimeControls.deferEvidenceUntilCameraSettled
|
||||
? { cameraSettled }
|
||||
: {}),
|
||||
...(sceneSeek
|
||||
? {
|
||||
timelineSeek: {
|
||||
shotElapsedSec: Number(sceneSeek.shotElapsedSec) || 0,
|
||||
flightDurationSec: Number(sceneSeek.flightDurationSec) || 0,
|
||||
holdDurationSec: Number(sceneSeek.holdDurationSec) || 0,
|
||||
holdElapsedSec: Number(sceneSeek.holdElapsedSec) || 0,
|
||||
cameraProgress: clamp01(Number(sceneSeek.cameraProgress) || 0),
|
||||
holdProgress: clamp01(Number(sceneSeek.holdProgress) || 0),
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
if (runtimeControls.evidencePathDuringCamera && cameraTravel) {
|
||||
state.params.sceneControls.cameraTravel = { ...cameraTravel };
|
||||
}
|
||||
}
|
||||
state.params.sceneContext = {
|
||||
sceneId: scene.id,
|
||||
sceneTitle: scene.title,
|
||||
shotId: shot.id,
|
||||
shotTitle: shot.title,
|
||||
shotIndex,
|
||||
shotCount: scene.shots.length,
|
||||
durationSec: shot.durationSec || DEFAULT_SHOT_DURATION_SEC,
|
||||
holdSec: effectiveShotHoldSec(scene, shot, packs, states),
|
||||
sceneElapsedSec,
|
||||
sceneDurationSec,
|
||||
sceneProgress:
|
||||
sceneDurationSec > 0 ? sceneElapsedSec / sceneDurationSec : 0,
|
||||
...(sceneSeek
|
||||
? {
|
||||
shotElapsedSec: Number(sceneSeek.shotElapsedSec) || 0,
|
||||
shotProgress: clamp01(Number(sceneSeek.shotProgress) || 0),
|
||||
seeking: true,
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
}
|
||||
for (const layerId of scene?.releaseLayerIds || []) {
|
||||
if (!Object.hasOwn(states, layerId)) states[layerId] = { enabled: false };
|
||||
}
|
||||
return states;
|
||||
}
|
||||
|
||||
/** Resolve recipe map preferences and registered presentation fallbacks. */
|
||||
export function visualStateForShot(shot, packs, isMapStackAvailable) {
|
||||
const sourceRecipe = packs.recipeForShot(shot);
|
||||
const sceneBeatState = Object.values(shot?.layers || {}).find(
|
||||
(state) => state?.params?.presentation === 'scene-beat',
|
||||
);
|
||||
const controls = {
|
||||
...sceneBeatState?.params?.sceneControls,
|
||||
...sourceRecipe?.runtimeControlsByBeat?.[sceneBeatState?.params?.beatId],
|
||||
};
|
||||
if (controls.imageryComparison === true) {
|
||||
return { ...(shot.visual || {}), mapStack: 'esri-imagery' };
|
||||
}
|
||||
if (
|
||||
sourceRecipe?.standaloneSurfaceBeatIds?.includes(
|
||||
sceneBeatState?.params?.beatId,
|
||||
)
|
||||
) {
|
||||
return { ...(shot.visual || {}), mapStack: 'esri-imagery' };
|
||||
}
|
||||
const visual = sourceRecipe?.photorealSurfaceBeatIds?.includes(
|
||||
sceneBeatState?.params?.beatId,
|
||||
)
|
||||
? { ...(shot.visual || {}), mapStack: 'photoreal' }
|
||||
: shot?.visual || {};
|
||||
return packs.resolveVisual(shot, visual, isMapStackAvailable);
|
||||
}
|
||||
|
||||
/** Combine authored holds, registered pack overrides and layer reveal requirements. */
|
||||
export function effectiveShotHoldSec(
|
||||
scene,
|
||||
shot,
|
||||
packs,
|
||||
resolvedStates = null,
|
||||
) {
|
||||
const states = resolvedStates || layerStatesForShot(scene, shot, packs);
|
||||
const recipe = packs.recipeForShot(shot);
|
||||
const overrides = Object.values(states)
|
||||
.filter(
|
||||
(state) => state?.enabled && state.params?.presentation === 'scene-beat',
|
||||
)
|
||||
.map((state) => recipe?.runtimeHoldSecByBeat?.[state.params.beatId])
|
||||
.filter((value) => Number.isFinite(value) && value >= 0);
|
||||
const authoredHoldSec = overrides.length
|
||||
? Math.max(...overrides)
|
||||
: Number(shot?.holdSec) || 0;
|
||||
return Object.values(states).reduce(
|
||||
(holdSec, state) => {
|
||||
const controls = state?.params?.sceneControls;
|
||||
return Math.max(
|
||||
holdSec,
|
||||
Number(controls?.minimumHoldSec) || 0,
|
||||
controls?.evidenceSequence === true
|
||||
? Number(controls.evidenceSequenceDurationSec) || 0
|
||||
: 0,
|
||||
);
|
||||
},
|
||||
Math.max(packs.minimumHoldSec(states), authoredHoldSec),
|
||||
);
|
||||
}
|
||||
|
||||
/** Estimate flight plus effective hold without mutating saved layer parameters. */
|
||||
export function shotRuntimeDurationSec(scene, shot, packs) {
|
||||
const states = deepClone(shot?.layers || {});
|
||||
const sourceRecipe = packs.recipeForShot(shot);
|
||||
for (const state of Object.values(states)) {
|
||||
if (state?.params?.presentation !== 'scene-beat') continue;
|
||||
const runtimeControls =
|
||||
sourceRecipe?.runtimeControlsByBeat?.[state.params.beatId];
|
||||
if (runtimeControls) {
|
||||
state.params.sceneControls = {
|
||||
...state.params.sceneControls,
|
||||
...runtimeControls,
|
||||
};
|
||||
}
|
||||
}
|
||||
return (
|
||||
(shot?.durationSec || DEFAULT_SHOT_DURATION_SEC) +
|
||||
effectiveShotHoldSec(scene, shot, packs, states)
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user