103 lines
4.3 KiB
TypeScript
103 lines
4.3 KiB
TypeScript
/**
|
||
* What the Animation tab shows above the timeline (#99): the root asset with every row's value at
|
||
* the playhead written over it. Nothing here touches the document – the holder returned is built
|
||
* fresh on every call from copies, and the preview draws it the way the Scene tab draws a scene.
|
||
*/
|
||
import {
|
||
animatableDefs,
|
||
animatedKindOf,
|
||
frameCount,
|
||
isPositionRow,
|
||
PLACED_ROOT_ID,
|
||
placedRoot,
|
||
playbackOf,
|
||
trackWriteAt,
|
||
} from './animation';
|
||
import type { AnimationAsset, AnimationTrack, Asset } from './Asset';
|
||
import { instanceChildId, objectRef } from './instance';
|
||
import { findAsset, type ObjectHolder, type Project } from './Project';
|
||
import { resolveSceneObjectProps } from '../properties/resolve';
|
||
|
||
/**
|
||
* The holder the preview draws at `frame`: a copy of the scene for a scene root, otherwise a holder
|
||
* of one object placing the root at the origin, its content resolved from the template like any
|
||
* placed container's. A row reaches an object stored in the holder directly and one resolved from a
|
||
* container through its instance's edits – the same route an edit in the Scene tab takes, so a row
|
||
* aimed into a placed container needs nothing of its own (#109). `undefined` without a root. Past
|
||
* the end of a `once-reset` pass nothing is written: the player has put the starting values back.
|
||
* `reverse` is ping-pong's way back, as `playheadAt` reports it.
|
||
*/
|
||
export function previewHolder(
|
||
project: Project,
|
||
animation: AnimationAsset,
|
||
frame: number,
|
||
reverse = false,
|
||
): ObjectHolder | undefined {
|
||
const root = findAsset(project, animation.root);
|
||
if (!root) return undefined;
|
||
const reset = playbackOf(animation) === 'once-reset' && frame >= frameCount(animation);
|
||
const holder = root.type === 'scene' ? sceneCopy(root) : placedHolder(root);
|
||
for (const track of reset ? [] : animation.tracks) {
|
||
if (root.type === 'scene' && track.target === '') writeScene(holder, track, frame, reverse);
|
||
else writeObject(project, holder, previewObjectId(root, track.target), track, frame, reverse);
|
||
}
|
||
return holder;
|
||
}
|
||
|
||
/** The id the preview draws a row's target under – for a check that wants to look at it. */
|
||
export function previewObjectId(root: Asset | undefined, target: string): string {
|
||
if (root?.type === 'scene') return target;
|
||
return target === '' ? PLACED_ROOT_ID : instanceChildId(PLACED_ROOT_ID, target);
|
||
}
|
||
|
||
/** Deep enough that nothing a row writes – a position, an override, an instance edit – is the document's. */
|
||
function sceneCopy(scene: ObjectHolder): ObjectHolder {
|
||
return {
|
||
...scene,
|
||
props: { ...scene.props },
|
||
objects: scene.objects.map((o) => ({
|
||
...o,
|
||
position: { ...o.position },
|
||
overrides: { ...o.overrides },
|
||
instanceEdits: structuredClone(o.instanceEdits),
|
||
})),
|
||
};
|
||
}
|
||
|
||
function placedHolder(root: Asset): ObjectHolder {
|
||
return { id: PLACED_ROOT_ID, type: 'container', name: root.name, props: {}, objects: [placedRoot(root)] };
|
||
}
|
||
|
||
/** A row on the scene itself: its value goes into the copy's own properties. */
|
||
function writeScene(holder: ObjectHolder, track: AnimationTrack, frame: number, reverse: boolean): void {
|
||
const def = animatableDefs('scene').find((d) => d.key === track.property);
|
||
if (!def) return;
|
||
const value = trackWriteAt(track, def.kind, frame, holder.props[track.property] ?? def.default, reverse);
|
||
if (value !== undefined) holder.props[track.property] = value;
|
||
}
|
||
|
||
function writeObject(
|
||
project: Project,
|
||
holder: ObjectHolder,
|
||
id: string,
|
||
track: AnimationTrack,
|
||
frame: number,
|
||
reverse: boolean,
|
||
): void {
|
||
const ref = objectRef(project, holder, id);
|
||
const asset = ref && findAsset(project, ref.object.assetId);
|
||
if (!ref || !asset) return;
|
||
// Resolved, so a shader worn through an override or an instance edit brings its parameters (#104).
|
||
const props = resolveSceneObjectProps(project, asset, ref.object);
|
||
const kind = animatedKindOf(project, asset, props, track.property);
|
||
if (!kind) return;
|
||
const key = track.property;
|
||
if (isPositionRow(asset.type, key)) {
|
||
const value = trackWriteAt(track, kind, frame, ref.object.position[key], reverse);
|
||
if (value !== undefined) ref.setPosition({ ...ref.object.position, [key]: Number(value) });
|
||
return;
|
||
}
|
||
const value = trackWriteAt(track, kind, frame, props[key], reverse);
|
||
if (value !== undefined) ref.setOverride(key, value);
|
||
}
|