The scene view and the animation preview share one camera, the three form dialogs one base, and keys are found through one set of helpers (#85)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
bf02c9e8b3
commit
2a390e52ff
22 files changed
+418
-452
No files matched your search
@@ -17,9 +17,9 @@ export type Playback = (typeof PLAYBACKS)[number];
|
||||
export const ANIMATION_ROOT_TYPES: readonly AssetType[] = ['sprite', 'text', 'container', 'scene'];
|
||||
|
||||
/** Position lives on the object, not in its property bag, so the timeline offers it by name. */
|
||||
export const POSITION_KEYS = ['x', 'y'] as const;
|
||||
const POSITION_KEYS = ['x', 'y'] as const;
|
||||
|
||||
export function isPositionKey(key: string): key is (typeof POSITION_KEYS)[number] {
|
||||
function isPositionKey(key: string): key is (typeof POSITION_KEYS)[number] {
|
||||
return (POSITION_KEYS as readonly string[]).includes(key);
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ export function fpsOf(asset: AnimationAsset): number {
|
||||
}
|
||||
|
||||
/** The frame of the last key over all rows; -1 for an animation without keys. */
|
||||
export function lastKeyFrame(asset: AnimationAsset): number {
|
||||
function lastKeyFrame(asset: AnimationAsset): number {
|
||||
let last = -1;
|
||||
for (const track of asset.tracks) {
|
||||
const key = track.keys[track.keys.length - 1];
|
||||
@@ -133,7 +133,7 @@ export function interpolationOf(key: AnimationKey): KeyInterpolation {
|
||||
* How far along the way to the next key a value is, `t` the share of the time passed. The eases are
|
||||
* quadratic: ease-in reaches 0.25 at half the time, ease-out 0.75, ease-in-out 0.5 but 0.125 at a quarter.
|
||||
*/
|
||||
export function easedShare(interpolation: KeyInterpolation, t: number): number {
|
||||
function easedShare(interpolation: KeyInterpolation, t: number): number {
|
||||
switch (interpolation) {
|
||||
case 'linear':
|
||||
return t;
|
||||
@@ -210,6 +210,16 @@ export function animatedKind(type: AssetType, key: string): string | undefined {
|
||||
return animatableDefs(type).find((d) => d.key === key)?.kind;
|
||||
}
|
||||
|
||||
/** The kind `key` has on an asset holding `bag` – see `animatableDefsOf`. */
|
||||
export function animatedKindOf(
|
||||
project: Project,
|
||||
asset: Asset,
|
||||
bag: PropertyBag,
|
||||
key: string,
|
||||
): string | undefined {
|
||||
return animatableDefsOf(project, asset, bag).find((d) => d.key === key)?.kind;
|
||||
}
|
||||
|
||||
function hasPosition(type: AssetType): boolean {
|
||||
return type !== 'scene' && type !== 'behavior';
|
||||
}
|
||||
@@ -501,6 +511,28 @@ export function pauseAnimation(holder: PlayerHolder, animationId: string): void
|
||||
if (animationId === '' || player.animationId === animationId) player.playing = false;
|
||||
}
|
||||
|
||||
/** A key on one row, as the timeline selects it. */
|
||||
export interface KeyRef {
|
||||
trackId: string;
|
||||
frame: number;
|
||||
}
|
||||
|
||||
export function sameKey(a: KeyRef, b: KeyRef): boolean {
|
||||
return a.trackId === b.trackId && a.frame === b.frame;
|
||||
}
|
||||
|
||||
export function hasKey(keys: readonly KeyRef[], key: KeyRef): boolean {
|
||||
return keys.some((k) => sameKey(k, key));
|
||||
}
|
||||
|
||||
export function findTrack(asset: AnimationAsset, trackId: string): AnimationTrack | undefined {
|
||||
return asset.tracks.find((t) => t.id === trackId);
|
||||
}
|
||||
|
||||
export function findKey(asset: AnimationAsset, ref: KeyRef): AnimationKey | undefined {
|
||||
return findTrack(asset, ref.trackId)?.keys.find((k) => k.frame === ref.frame);
|
||||
}
|
||||
|
||||
/** Sorted by frame, at most one per frame (the later wins), frames whole and not negative. */
|
||||
export function normalizeKeys(keys: readonly AnimationKey[]): AnimationKey[] {
|
||||
const byFrame = new Map<number, AnimationKey>();
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
import {
|
||||
animatableDefs,
|
||||
animatableDefsOf,
|
||||
animatedKindOf,
|
||||
frameCount,
|
||||
isPositionRow,
|
||||
PLACED_ROOT_ID,
|
||||
@@ -18,9 +18,6 @@ import { instanceChildId, objectRef } from './instance';
|
||||
import { findAsset, type ObjectHolder, type Project } from './Project';
|
||||
import { resolveSceneObjectProps } from '../properties/resolve';
|
||||
|
||||
/** The one object the preview places the root on – what a row aimed at the root itself writes to. */
|
||||
export const PREVIEW_ROOT_ID = PLACED_ROOT_ID;
|
||||
|
||||
/**
|
||||
* 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
|
||||
@@ -50,7 +47,7 @@ export function previewHolder(
|
||||
/** 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 === '' ? PREVIEW_ROOT_ID : instanceChildId(PREVIEW_ROOT_ID, 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. */
|
||||
@@ -68,7 +65,7 @@ function sceneCopy(scene: ObjectHolder): ObjectHolder {
|
||||
}
|
||||
|
||||
function placedHolder(root: Asset): ObjectHolder {
|
||||
return { id: PREVIEW_ROOT_ID, type: 'container', name: root.name, props: {}, objects: [placedRoot(root)] };
|
||||
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. */
|
||||
@@ -92,7 +89,7 @@ function writeObject(
|
||||
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 = animatableDefsOf(project, asset, props).find((d) => d.key === track.property)?.kind;
|
||||
const kind = animatedKindOf(project, asset, props, track.property);
|
||||
if (!kind) return;
|
||||
const key = track.property;
|
||||
if (isPositionRow(asset.type, key)) {
|
||||
|
||||
Reference in new issue
Block a user