Files
feuerwerk/src/core/model/Asset.ts
T

271 lines
8.9 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.
import type { Graph } from '../graph/Graph';
import type { PropertyDef } from '../properties/PropertySchema';
import type { AssetType } from './AssetTypes';
import type { PropertyBag, PropertyValue } from './Properties';
import type { Scope } from './Scope';
import type { SceneObject } from './SceneObject';
export interface AssetBase {
id: string;
type: AssetType;
name: string;
props: PropertyBag;
}
export interface SpriteAsset extends AssetBase {
type: 'sprite';
}
export interface CameraAsset extends AssetBase {
type: 'camera';
}
export interface TextAsset extends AssetBase {
type: 'text';
}
export interface SceneAsset extends AssetBase {
type: 'scene';
objects: SceneObject[];
}
/** `variables` is this asset's own property schema; `props` holds their default values. */
export interface BehaviorAsset extends AssetBase {
type: 'behavior';
variables: PropertyDef[];
graph: Graph;
}
/**
* Declarations shared by the whole run: a `Variables` node naming this asset reads and writes one
* value per key, however many graphs point at it. Nothing is placed, so nothing overrides it –
* `props` holds the starting values, as a behavior's does for its own variables.
*/
export interface VariablesAsset extends AssetBase {
type: 'variables';
variables: PropertyDef[];
}
/** Placed as a child of the object it belongs to; its position is the offset from that body. */
export interface ColliderAsset extends AssetBase {
type: 'collider';
}
/**
* Placed as a child of the object it moves – at most one per object. Its props say how the physics
* engine treats that object: falling, resting or pushed along by the graph (see `properties/rigidbody.ts`).
*/
export interface RigidbodyAsset extends AssetBase {
type: 'rigidbody';
}
/**
* `objects` is a template, not content: a placed container shows it without copying it, so an
* edit here reaches every instance (see `model/instance.ts`). Positions are local to the
* container's origin.
*/
export interface ContainerAsset extends AssetBase {
type: 'container';
objects: SceneObject[];
}
/** How the parts between the corners of a nine-slice frame fill the space they are given (#41). */
export type FrameFill = 'stretch' | 'repeat';
/** Distances from the frame's own edges, in image pixels; all zero means the frame is not sliced. */
export interface FrameBorders {
left: number;
top: number;
right: number;
bottom: number;
}
/**
* `id` shares the generator with asset ids and is unique across the document (see `model/texture.ts`).
* `border` and `fill` are absent on a frame nobody sliced, which is what every document written
* before #41 says – read them through `frameBorders`/`frameFill`, never off the object.
*/
export interface TextureFrame {
id: string;
name: string;
x: number;
y: number;
w: number;
h: number;
border?: FrameBorders;
fill?: FrameFill;
}
/**
* `file` names the image in `data/textures/` and is content-addressed (`{sha256}.{ext}`), so it
* can be cached forever; empty means the upload failed or has not happened yet. `width`/`height`
* are the size as uploaded, so frames can be sliced and validated before the image loads.
*/
export interface TextureAsset extends AssetBase {
type: 'texture';
file: string;
width: number;
height: number;
frames: TextureFrame[];
}
/** How a key blends into the next one (#101); absent is `linear`. `step` holds until the next key. */
export type KeyInterpolation = 'linear' | 'step' | 'ease-in' | 'ease-out' | 'ease-in-out';
/**
* A value on a whole frame. Its interpolation rules the way to the next key. A value that cannot
* blend (a texture, a switch) holds until the next key whatever it says.
*/
export interface AnimationKey {
frame: number;
value: PropertyValue;
interpolation?: KeyInterpolation;
}
/**
* One row of the timeline: a property of the root (`target` empty) or of an object inside it
* (`target` = the template object's id). `keys` are sorted by frame, at most one per frame.
* `relative` adds a number to the value the target had when the animation started.
*/
export interface AnimationTrack {
id: string;
target: string;
property: string;
relative?: boolean;
keys: AnimationKey[];
}
/**
* A timeline over one root asset – a sprite, text, container or scene. It is played on an object,
* and its rows reach that object and the objects inside it, so every instance plays on its own.
* The last key decides how long it runs (see `model/animation.ts`).
*/
export interface AnimationAsset extends AssetBase {
type: 'animation';
root: string;
tracks: AnimationTrack[];
}
/**
* The graph holds one `State` node per state and an edge per transition; a behavior reads the
* machine's current state through `State branch` and moves it through `Set state` or `State transition`.
* A `global` machine has one current state for the whole run instead of one per behavior instance.
*
* `start` is the id of the state every instance begins in, empty when the machine has no start –
* then it is in no state until `Set state` runs. It lives on the asset rather than on the node
* because only one state may be the start, and that is structural here instead of a rule someone
* has to keep. A deleted state leaves a dead id behind, which reads as no start.
*/
export interface StateMachineAsset extends AssetBase {
type: 'statemachine';
graph: Graph;
scope?: Scope;
start?: string;
}
/**
* The graph is GLSL in nodes: `Property` nodes declare the sprite rows, the `Output` node ends
* the fragment (see `core/shader/compile.ts`). A sprite wears it through its `shader` property.
*/
export interface ShaderAsset extends AssetBase {
type: 'shader';
graph: Graph;
}
/**
* `file` names the typeface in `data/fonts/` the way a texture names its image: content-addressed
* `{sha256}.{ext}`, empty when the upload failed. A text object wears it through its `font`
* property; the family the browser knows it under is derived from the file name, see `model/font`.
*/
export interface FontAsset extends AssetBase {
type: 'font';
file: string;
}
/**
* `file` names the sound in `data/sounds/` the way a font names its typeface: content-addressed
* `{sha256}.{ext}`, empty when the upload failed. Nothing wears a sound – a graph plays it with
* `Play sound`, and `props` says how loud and whether it repeats (see `properties/sounds.ts`).
*/
export interface SoundAsset extends AssetBase {
type: 'sound';
file: string;
}
/**
* A tile a tilemap paints with: `pictures` names one block per animation step (a texture id for a
* sheet that holds nothing else, a frame id for one block cut out of a sheet), and `props` say
* which kind of block it is – a plain tile or one of the two autotile blocks that draw their own
* edges – how fast it runs and whether it loops or bounces. What a block is cut into is
* `model/tile.ts`, which quarter goes where `model/autotile.ts`; placing one is the tilemap of #128.
*/
export interface TileAsset extends AssetBase {
type: 'tile';
pictures: string[];
}
/**
* One drawing layer of a tilemap: `cells` maps `"x,y"` to the id of the tile lying there, and a
* layer nobody switched off is drawn. Which of that tile's quarters a cell shows is worked out
* from its neighbours while drawing, never stored – see `model/tilemap.ts`.
*/
export interface TilemapLayer {
id: string;
name: string;
hidden?: boolean;
cells: Record<string, string>;
}
/**
* A grid painted with tiles, placed in a scene as a sprite is. How wide the grid is and how
* big one cell is are properties, so the inspector edits them with undo like any other row; the
* layers are intrinsic data, as a texture's frames are (#128).
*/
export interface TilemapAsset extends AssetBase {
type: 'tilemap';
layers: TilemapLayer[];
}
/**
* The graph is the motion of one particle: every output of its `Output` node is a function of the
* particle's age and its own random number (see `core/particles/compile.ts`). A placed object is
* the source that emits them; how many and for how long are its properties.
*/
export interface ParticlesAsset extends AssetBase {
type: 'particles';
graph: Graph;
}
export type Asset =
| SpriteAsset
| CameraAsset
| TextAsset
| SceneAsset
| BehaviorAsset
| VariablesAsset
| ColliderAsset
| RigidbodyAsset
| ContainerAsset
| TextureAsset
| AnimationAsset
| TileAsset
| TilemapAsset
| StateMachineAsset
| ShaderAsset
| FontAsset
| SoundAsset
| ParticlesAsset;
/** The assets edited as a node graph – what the graph canvas and the graph commands work on. */
export type GraphAsset = BehaviorAsset | StateMachineAsset | ShaderAsset | ParticlesAsset;
const GRAPH_TYPES: ReadonlySet<AssetType> = new Set(['behavior', 'statemachine', 'shader', 'particles']);
export function isGraphAsset(asset: Asset): asset is GraphAsset {
return GRAPH_TYPES.has(asset.type);
}
export function isSceneAsset(asset: Asset): asset is SceneAsset {
return asset.type === 'scene';
}