Files
feuerwerk/src/core/model/animationPreview.ts
T
2026-09-17 14:19:53 +02:00

103 lines
4.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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);
}