@omnimedia/omnitool 1.1.0-77 → 1.1.0-79
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +111 -7
- package/package.json +1 -1
- package/s/demo/routines/timeline-setup.ts +37 -22
- package/s/driver/fns/schematic.ts +2 -0
- package/s/driver/parts/compositor.ts +6 -0
- package/s/timeline/index.ts +2 -0
- package/s/timeline/parts/animations.ts +24 -0
- package/s/timeline/parts/filters.ts +3 -34
- package/s/timeline/parts/item.ts +24 -1
- package/s/timeline/renderers/parts/handy.ts +53 -36
- package/s/timeline/renderers/parts/samplers/visual/parts/sample.ts +9 -8
- package/s/timeline/renderers/parts/samplers/visual/parts/sequence.ts +4 -4
- package/s/timeline/sugar/helpers.ts +77 -5
- package/s/timeline/sugar/o.ts +97 -4
- package/s/timeline/types.ts +26 -3
- package/s/timeline/utils/anim.ts +71 -0
- package/s/timeline/utils/terps.ts +81 -0
- package/x/demo/demo.bundle.min.js +104 -104
- package/x/demo/demo.bundle.min.js.map +4 -4
- package/x/demo/routines/timeline-setup.js +16 -4
- package/x/demo/routines/timeline-setup.js.map +1 -1
- package/x/driver/fns/schematic.d.ts +2 -0
- package/x/driver/parts/compositor.js +5 -0
- package/x/driver/parts/compositor.js.map +1 -1
- package/x/index.html +2 -2
- package/x/tests.bundle.min.js +106 -106
- package/x/tests.bundle.min.js.map +4 -4
- package/x/tests.html +1 -1
- package/x/timeline/index.d.ts +2 -0
- package/x/timeline/index.js +2 -0
- package/x/timeline/index.js.map +1 -1
- package/x/timeline/parts/animations.d.ts +25 -0
- package/x/timeline/parts/animations.js +12 -0
- package/x/timeline/parts/animations.js.map +1 -0
- package/x/timeline/parts/filters.d.ts +3 -8
- package/x/timeline/parts/filters.js +1 -16
- package/x/timeline/parts/filters.js.map +1 -1
- package/x/timeline/parts/item.d.ts +24 -5
- package/x/timeline/parts/item.js +5 -3
- package/x/timeline/parts/item.js.map +1 -1
- package/x/timeline/renderers/parts/handy.d.ts +13 -7
- package/x/timeline/renderers/parts/handy.js +22 -20
- package/x/timeline/renderers/parts/handy.js.map +1 -1
- package/x/timeline/renderers/parts/samplers/visual/parts/sample.d.ts +3 -2
- package/x/timeline/renderers/parts/samplers/visual/parts/sample.js +6 -5
- package/x/timeline/renderers/parts/samplers/visual/parts/sample.js.map +1 -1
- package/x/timeline/renderers/parts/samplers/visual/parts/sequence.d.ts +3 -2
- package/x/timeline/renderers/parts/samplers/visual/parts/sequence.js +2 -2
- package/x/timeline/renderers/parts/samplers/visual/parts/sequence.js.map +1 -1
- package/x/timeline/sugar/helpers.d.ts +21 -4
- package/x/timeline/sugar/helpers.js +45 -2
- package/x/timeline/sugar/helpers.js.map +1 -1
- package/x/timeline/sugar/o.d.ts +18 -1
- package/x/timeline/sugar/o.js +72 -2
- package/x/timeline/sugar/o.js.map +1 -1
- package/x/timeline/types.d.ts +12 -2
- package/x/timeline/utils/anim.d.ts +5 -0
- package/x/timeline/utils/anim.js +44 -0
- package/x/timeline/utils/anim.js.map +1 -0
- package/x/timeline/utils/terps.d.ts +11 -0
- package/x/timeline/utils/terps.js +57 -0
- package/x/timeline/utils/terps.js.map +1 -0
|
@@ -2,19 +2,20 @@
|
|
|
2
2
|
import {SampleContext} from "./types.js"
|
|
3
3
|
import {sampleSequence} from "./sequence.js"
|
|
4
4
|
import {Ms} from "../../../../../../units/ms.js"
|
|
5
|
-
import {
|
|
5
|
+
import {Item, Kind, SpatialItem} from "../../../../../parts/item.js"
|
|
6
6
|
import {FilterSpec, Layer} from "../../../../../../driver/fns/schematic.js"
|
|
7
|
-
import {
|
|
7
|
+
import {AncestorAt, computeOpacity, computeWorldMatrix} from "../../../handy.js"
|
|
8
8
|
|
|
9
9
|
export async function sampleVisual(
|
|
10
10
|
ctx: SampleContext,
|
|
11
11
|
item: Item.Any,
|
|
12
12
|
time: Ms,
|
|
13
|
-
ancestors:
|
|
13
|
+
ancestors: AncestorAt[]
|
|
14
14
|
): Promise<Layer[]> {
|
|
15
|
-
const matrix = computeWorldMatrix(ctx.items, ancestors, item)
|
|
15
|
+
const matrix = computeWorldMatrix(ctx.items, ancestors, item, time)
|
|
16
|
+
const alpha = computeOpacity(ctx, item, time)
|
|
16
17
|
const crop = "spatialId" in item && item.spatialId
|
|
17
|
-
? (ctx.items.get(item.spatialId) as
|
|
18
|
+
? (ctx.items.get(item.spatialId) as SpatialItem | undefined)?.crop
|
|
18
19
|
: undefined
|
|
19
20
|
const filters = "filterIds" in item && item.filterIds
|
|
20
21
|
? item.filterIds
|
|
@@ -25,7 +26,7 @@ export async function sampleVisual(
|
|
|
25
26
|
|
|
26
27
|
switch (item.kind) {
|
|
27
28
|
case Kind.Stack: {
|
|
28
|
-
const nextAnc = [...ancestors, item]
|
|
29
|
+
const nextAnc = [...ancestors, {item, localTime: time}]
|
|
29
30
|
|
|
30
31
|
const layers = await Promise.all(
|
|
31
32
|
item.childrenIds
|
|
@@ -44,7 +45,7 @@ export async function sampleVisual(
|
|
|
44
45
|
if (time < 0 || time >= item.duration) return []
|
|
45
46
|
|
|
46
47
|
const frame = await ctx.videoSampler(item, time)
|
|
47
|
-
return frame ? [{kind: "image", frame, matrix, crop, filters, id: item.id}] : []
|
|
48
|
+
return frame ? [{kind: "image", frame, matrix, alpha, crop, filters, id: item.id}] : []
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
case Kind.Text: {
|
|
@@ -54,7 +55,7 @@ export async function sampleVisual(
|
|
|
54
55
|
? (ctx.items.get(item.styleId) as Item.TextStyle)?.style
|
|
55
56
|
: undefined
|
|
56
57
|
|
|
57
|
-
return [{id: item.id, kind: "text", content: item.content, style, matrix, crop, filters}]
|
|
58
|
+
return [{id: item.id, kind: "text", content: item.content, style, matrix, alpha, crop, filters}]
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
case Kind.Gap: {
|
|
@@ -3,21 +3,21 @@ import {sampleVisual} from "./sample.js"
|
|
|
3
3
|
import {SampleContext} from "./types.js"
|
|
4
4
|
import {sampleTransition} from "./transition.js"
|
|
5
5
|
import {ms, Ms} from "../../../../../../units/ms.js"
|
|
6
|
-
import {
|
|
6
|
+
import {Item, Kind} from "../../../../../parts/item.js"
|
|
7
7
|
import {Layer} from "../../../../../../driver/fns/schematic.js"
|
|
8
|
-
import {
|
|
8
|
+
import {AncestorAt, computeItemDuration} from "../../../handy.js"
|
|
9
9
|
|
|
10
10
|
export async function sampleSequence(
|
|
11
11
|
ctx: SampleContext,
|
|
12
12
|
seq: Item.Sequence,
|
|
13
13
|
time: Ms,
|
|
14
|
-
ancestors:
|
|
14
|
+
ancestors: AncestorAt[]
|
|
15
15
|
): Promise<Layer[]> {
|
|
16
16
|
const state = sampleSequenceAt(ctx, seq, time)
|
|
17
17
|
|
|
18
18
|
if (!state) return []
|
|
19
19
|
|
|
20
|
-
const nextAnc = [...ancestors, seq]
|
|
20
|
+
const nextAnc = [...ancestors, {item: seq, localTime: time}]
|
|
21
21
|
|
|
22
22
|
if (!state.isTransitioning) {
|
|
23
23
|
return sampleVisual(ctx, state.item, state.localTime, nextAnc)
|
|
@@ -2,14 +2,18 @@
|
|
|
2
2
|
import {TextStyleOptions} from "pixi.js"
|
|
3
3
|
|
|
4
4
|
import {O} from "./o.js"
|
|
5
|
-
import {Transform} from "../types.js"
|
|
6
5
|
import {Media} from "../parts/media.js"
|
|
7
6
|
import {TimelineFile} from "../parts/basics.js"
|
|
8
7
|
import {FilterAction} from "../parts/filters.js"
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
8
|
+
import {visualAnimations} from "../parts/animations.js"
|
|
9
|
+
import {filters, FilterParams, FilterType} from "../parts/filters.js"
|
|
10
|
+
import {Crop, FilterableItem, Item, VisualAnimatableItem} from "../parts/item.js"
|
|
11
|
+
import {Anim, AnimateAction, Interpolation, Keyframes, TrackTransform, Transform, Vec2, VisualAnimations} from "../types.js"
|
|
11
12
|
|
|
12
13
|
export type Build<T extends Item.Any = Item.Any> = (o: O) => T
|
|
14
|
+
type BuildVisualAnimateActions = {
|
|
15
|
+
[TKey in keyof VisualAnimations]-?: BuildAnimateAction
|
|
16
|
+
}
|
|
13
17
|
|
|
14
18
|
function createTimeline(): TimelineFile {
|
|
15
19
|
return {
|
|
@@ -75,13 +79,61 @@ export function spatial(transform?: Transform, crop?: Crop): Build<Item.Spatial>
|
|
|
75
79
|
return o => o.spatial(transform, crop)
|
|
76
80
|
}
|
|
77
81
|
|
|
82
|
+
export function animatedSpatial(anim: Anim<TrackTransform>, crop?: Crop): Build<Item.AnimatedSpatial> {
|
|
83
|
+
return o => o.animatedSpatial(anim, crop)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export const anim = {
|
|
87
|
+
scalar(terp: Interpolation, track: Keyframes): Anim<Keyframes> {
|
|
88
|
+
return {terp, track}
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
vec2(terp: Interpolation, source: Keyframes<Vec2>): Anim<{x: Keyframes, y: Keyframes}> {
|
|
92
|
+
const track = {x: [] as Keyframes, y: [] as Keyframes}
|
|
93
|
+
|
|
94
|
+
for (const [time, [x, y]] of source) {
|
|
95
|
+
track.x.push([time, x])
|
|
96
|
+
track.y.push([time, y])
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return {terp, track}
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
transform(terp: Interpolation, source: Keyframes<Transform>): Anim<TrackTransform> {
|
|
103
|
+
const track: TrackTransform = {
|
|
104
|
+
position: {x: [], y: []},
|
|
105
|
+
scale: {x: [], y: []},
|
|
106
|
+
rotation: [],
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
for (const [time, [position, scale, rotation]] of source) {
|
|
110
|
+
track.position.x.push([time, position[0]])
|
|
111
|
+
track.position.y.push([time, position[1]])
|
|
112
|
+
track.scale.x.push([time, scale[0]])
|
|
113
|
+
track.scale.y.push([time, scale[1]])
|
|
114
|
+
track.rotation.push([time, rotation])
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return {terp, track}
|
|
118
|
+
},
|
|
119
|
+
}
|
|
120
|
+
|
|
78
121
|
interface BuildFilterAction<TFilter extends FilterType> {
|
|
79
122
|
<T extends FilterableItem>(item: Build<T>, params?: FilterParams<TFilter>): Build<T>
|
|
80
123
|
make(params?: FilterParams<TFilter>): Build<Item.Filter<TFilter>>
|
|
81
124
|
}
|
|
82
125
|
|
|
126
|
+
interface BuildAnimateAction {
|
|
127
|
+
<T extends VisualAnimatableItem>(
|
|
128
|
+
item: Build<T>,
|
|
129
|
+
terp: Interpolation,
|
|
130
|
+
track: Keyframes
|
|
131
|
+
): Build<T>
|
|
132
|
+
make(terp: Interpolation, track: Keyframes): Build<Item.Animation>
|
|
133
|
+
}
|
|
134
|
+
|
|
83
135
|
type BuildFilterActions = {
|
|
84
|
-
[TName in keyof typeof
|
|
136
|
+
[TName in keyof typeof filters]: BuildFilterAction<(typeof filters)[TName]["type"]>
|
|
85
137
|
}
|
|
86
138
|
|
|
87
139
|
function makeFilter<TFilter extends FilterType>(
|
|
@@ -96,7 +148,7 @@ function makeFilter<TFilter extends FilterType>(
|
|
|
96
148
|
}
|
|
97
149
|
|
|
98
150
|
function makeFilters(): BuildFilterActions {
|
|
99
|
-
const names = Object.keys(
|
|
151
|
+
const names = Object.keys(filters) as (keyof typeof filters)[]
|
|
100
152
|
const entries = names.map(name => [
|
|
101
153
|
name,
|
|
102
154
|
makeFilter(o => o.filter[name] as FilterAction<any>)
|
|
@@ -106,6 +158,26 @@ function makeFilters(): BuildFilterActions {
|
|
|
106
158
|
|
|
107
159
|
export const filter = makeFilters()
|
|
108
160
|
|
|
161
|
+
function makeAnimate(
|
|
162
|
+
get: (o: O) => AnimateAction
|
|
163
|
+
): BuildAnimateAction {
|
|
164
|
+
const action = (<T extends VisualAnimatableItem>(
|
|
165
|
+
item: Build<T>,
|
|
166
|
+
terp: Interpolation,
|
|
167
|
+
track: Keyframes
|
|
168
|
+
): Build<T> => o => get(o)(item(o), terp, track)) as BuildAnimateAction
|
|
169
|
+
action.make = (terp: Interpolation, track: Keyframes) => o => get(o).make(terp, track)
|
|
170
|
+
return action
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function makeAnimateActions(): BuildVisualAnimateActions {
|
|
174
|
+
const entries = Object.keys(visualAnimations)
|
|
175
|
+
.map(key => [key, makeAnimate(o => o.animate[key as keyof VisualAnimations] as AnimateAction)])
|
|
176
|
+
return Object.fromEntries(entries) as BuildVisualAnimateActions
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export const animate = makeAnimateActions()
|
|
180
|
+
|
|
109
181
|
export function textStyle(style: TextStyleOptions): Build<Item.TextStyle> {
|
|
110
182
|
return o => o.textStyle(style)
|
|
111
183
|
}
|
package/s/timeline/sugar/o.ts
CHANGED
|
@@ -3,10 +3,15 @@ import {TextStyleOptions} from "pixi.js"
|
|
|
3
3
|
|
|
4
4
|
import {Media} from "../parts/media.js"
|
|
5
5
|
import {Id, TimelineFile} from "../parts/basics.js"
|
|
6
|
-
import {Transform, TransformOptions, Vec2} from "../types.js"
|
|
7
6
|
import {FilterAction, FilterActions} from "../parts/filters.js"
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
7
|
+
import {filters, FilterParams, FilterType} from "../parts/filters.js"
|
|
8
|
+
import {visualAnimations} from "../parts/animations.js"
|
|
9
|
+
import {Crop, Effect, FilterableItem, Item, Kind, VisualAnimatableItem} from "../parts/item.js"
|
|
10
|
+
import {Anim, AnimateAction, Interpolation, Keyframes, TrackTransform, Transform, TransformOptions, Vec2, VisualAnimations} from "../types.js"
|
|
11
|
+
|
|
12
|
+
type VisualAnimateActions = {
|
|
13
|
+
[TKey in keyof VisualAnimations]-?: AnimateAction
|
|
14
|
+
}
|
|
10
15
|
|
|
11
16
|
export class O {
|
|
12
17
|
constructor(public state: {timeline: TimelineFile}) {}
|
|
@@ -59,6 +64,62 @@ export class O {
|
|
|
59
64
|
return item
|
|
60
65
|
}
|
|
61
66
|
|
|
67
|
+
animatedSpatial = (anim: Anim<TrackTransform>, crop?: Crop): Item.AnimatedSpatial => {
|
|
68
|
+
const item: Item.AnimatedSpatial = {
|
|
69
|
+
id: this.getId(),
|
|
70
|
+
kind: Kind.AnimatedSpatial,
|
|
71
|
+
anim,
|
|
72
|
+
crop,
|
|
73
|
+
enabled: true
|
|
74
|
+
}
|
|
75
|
+
this.register(item)
|
|
76
|
+
return item
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
#registerAnimation = (anims: VisualAnimations): Item.Animation => {
|
|
80
|
+
const item: Item.Animation = {
|
|
81
|
+
id: this.getId(),
|
|
82
|
+
kind: Kind.Animation,
|
|
83
|
+
anims,
|
|
84
|
+
enabled: true
|
|
85
|
+
}
|
|
86
|
+
this.register(item)
|
|
87
|
+
return item
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
anim = {
|
|
91
|
+
scalar: (terp: Interpolation, track: Keyframes): Anim<Keyframes> => ({terp, track}),
|
|
92
|
+
|
|
93
|
+
vec2: (terp: Interpolation, source: Keyframes<Vec2>) => {
|
|
94
|
+
const track = {x: [] as Keyframes, y: [] as Keyframes}
|
|
95
|
+
|
|
96
|
+
for (const [time, [x, y]] of source) {
|
|
97
|
+
track.x.push([time, x])
|
|
98
|
+
track.y.push([time, y])
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return {terp, track}
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
transform: (terp: Interpolation, source: Keyframes<Transform>): Anim<TrackTransform> => {
|
|
105
|
+
const track: TrackTransform = {
|
|
106
|
+
position: {x: [], y: []},
|
|
107
|
+
scale: {x: [], y: []},
|
|
108
|
+
rotation: [],
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
for (const [time, [position, scale, rotation]] of source) {
|
|
112
|
+
track.position.x.push([time, position[0]])
|
|
113
|
+
track.position.y.push([time, position[1]])
|
|
114
|
+
track.scale.x.push([time, scale[0]])
|
|
115
|
+
track.scale.y.push([time, scale[1]])
|
|
116
|
+
track.rotation.push([time, rotation])
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return {terp, track}
|
|
120
|
+
},
|
|
121
|
+
}
|
|
122
|
+
|
|
62
123
|
#makeFilter = <TFilter extends FilterType>(type: TFilter) => {
|
|
63
124
|
const make = (params?: FilterParams<TFilter>) => {
|
|
64
125
|
const item: Item.Filter<TFilter> = {
|
|
@@ -87,13 +148,45 @@ export class O {
|
|
|
87
148
|
}
|
|
88
149
|
|
|
89
150
|
#makeFilters = (): FilterActions => {
|
|
90
|
-
const entries = Object.entries(
|
|
151
|
+
const entries = Object.entries(filters)
|
|
91
152
|
.map(([name, filter]) => [name, this.#makeFilter(filter.type)])
|
|
92
153
|
return Object.fromEntries(entries) as FilterActions
|
|
93
154
|
}
|
|
94
155
|
|
|
95
156
|
filter = this.#makeFilters()
|
|
96
157
|
|
|
158
|
+
#makeAnimate = <TKey extends keyof VisualAnimations>(key: TKey): AnimateAction => {
|
|
159
|
+
const make = (terp: Interpolation, track: Keyframes) =>
|
|
160
|
+
this.#registerAnimation({
|
|
161
|
+
[key]: this.anim.scalar(terp, track)
|
|
162
|
+
} as Pick<VisualAnimations, TKey>)
|
|
163
|
+
|
|
164
|
+
const action = (<T extends VisualAnimatableItem>(
|
|
165
|
+
item: T,
|
|
166
|
+
terp: Interpolation,
|
|
167
|
+
track: Keyframes
|
|
168
|
+
): T => {
|
|
169
|
+
const animation = make(terp, track)
|
|
170
|
+
const next = {
|
|
171
|
+
...item,
|
|
172
|
+
animationId: animation.id
|
|
173
|
+
}
|
|
174
|
+
this.set<T>(item.id, next as Partial<T>)
|
|
175
|
+
return next
|
|
176
|
+
}) as AnimateAction
|
|
177
|
+
|
|
178
|
+
action.make = make
|
|
179
|
+
return action
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
#makeAnimateActions = (): VisualAnimateActions => {
|
|
183
|
+
const entries = Object.keys(visualAnimations)
|
|
184
|
+
.map(key => [key, this.#makeAnimate(key as keyof VisualAnimations)])
|
|
185
|
+
return Object.fromEntries(entries) as VisualAnimateActions
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
animate = this.#makeAnimateActions()
|
|
189
|
+
|
|
97
190
|
sequence = (...items: Item.Any[]): Item.Sequence => {
|
|
98
191
|
const item = {
|
|
99
192
|
id: this.getId(),
|
package/s/timeline/types.ts
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
|
+
import {Item, VisualAnimatableItem} from "./parts/item.js"
|
|
2
|
+
|
|
3
|
+
export type Interpolation =
|
|
4
|
+
| "linear"
|
|
5
|
+
| "ease"
|
|
6
|
+
| "easeIn"
|
|
7
|
+
| "easeOut"
|
|
8
|
+
| "bounce"
|
|
9
|
+
| "catmullRom"
|
|
1
10
|
|
|
2
|
-
export type Interpolation = "linear" | "catmullRom"
|
|
3
11
|
export type Keyframe<Value = number> = [time: number, value: Value]
|
|
4
12
|
export type Keyframes<Value = number> = Keyframe<Value>[]
|
|
5
13
|
export type Vec2 = [x: number, y: number]
|
|
@@ -15,14 +23,29 @@ export type Anim<T> = {
|
|
|
15
23
|
track: T
|
|
16
24
|
}
|
|
17
25
|
|
|
18
|
-
export type Animations = Anim<TrackTransform>
|
|
19
|
-
|
|
20
26
|
export type TrackTransform = {
|
|
21
27
|
position: TrackVec2
|
|
22
28
|
scale: TrackVec2
|
|
23
29
|
rotation: Keyframes
|
|
24
30
|
}
|
|
25
31
|
|
|
32
|
+
export type ScalarAnimation = Anim<Keyframes>
|
|
33
|
+
export type Vec2Animation = Anim<TrackVec2>
|
|
34
|
+
export type TransformAnimation = Anim<TrackTransform>
|
|
35
|
+
export type VisualAnimations = {
|
|
36
|
+
opacity?: ScalarAnimation
|
|
37
|
+
}
|
|
38
|
+
export interface AnimateAction {
|
|
39
|
+
<T extends VisualAnimatableItem>(
|
|
40
|
+
item: T,
|
|
41
|
+
terp: Interpolation,
|
|
42
|
+
track: Keyframes
|
|
43
|
+
): T
|
|
44
|
+
make(terp: Interpolation, track: Keyframes): Item.Animation
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// export type Animations = Anim<TrackTransform>
|
|
48
|
+
|
|
26
49
|
export type TransformOptions = {
|
|
27
50
|
position?: Vec2
|
|
28
51
|
scale?: Vec2
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
|
|
2
|
+
import {resolveTerp} from "./terps.js"
|
|
3
|
+
import {Item, Kind} from "../parts/item.js"
|
|
4
|
+
import {Anim, Keyframes, ScalarAnimation, TrackTransform, Transform} from "../types.js"
|
|
5
|
+
|
|
6
|
+
const resolveScalar =(
|
|
7
|
+
time: number,
|
|
8
|
+
keys: Keyframes,
|
|
9
|
+
terp: Anim<Keyframes>["terp"],
|
|
10
|
+
): number => {
|
|
11
|
+
if (keys.length === 0)
|
|
12
|
+
return 0
|
|
13
|
+
|
|
14
|
+
if (keys.length === 1)
|
|
15
|
+
return keys[0][1]
|
|
16
|
+
|
|
17
|
+
const sorted = [...keys].sort((a, b) => a[0] - b[0])
|
|
18
|
+
|
|
19
|
+
if (time <= sorted[0][0])
|
|
20
|
+
return sorted[0][1]
|
|
21
|
+
|
|
22
|
+
const last = sorted[sorted.length - 1]
|
|
23
|
+
if (time >= last[0])
|
|
24
|
+
return last[1]
|
|
25
|
+
|
|
26
|
+
let index = 0
|
|
27
|
+
for (let i = 0; i < sorted.length - 1; i++) {
|
|
28
|
+
const a = sorted[i]
|
|
29
|
+
const b = sorted[i + 1]
|
|
30
|
+
if (time >= a[0] && time <= b[0]) {
|
|
31
|
+
index = i
|
|
32
|
+
break
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const [t2] = sorted[index]
|
|
37
|
+
const [t3] = sorted[index + 1]
|
|
38
|
+
const span = t3 - t2
|
|
39
|
+
const x = span === 0 ? 0 : (time - t2) / span
|
|
40
|
+
|
|
41
|
+
return resolveTerp(terp, x, sorted, index)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const resolveTransformAnimation =(
|
|
45
|
+
time: number,
|
|
46
|
+
anim: Anim<TrackTransform>,
|
|
47
|
+
): Transform => ([
|
|
48
|
+
[
|
|
49
|
+
resolveScalar(time, anim.track.position.x, anim.terp),
|
|
50
|
+
resolveScalar(time, anim.track.position.y, anim.terp),
|
|
51
|
+
],
|
|
52
|
+
[
|
|
53
|
+
resolveScalar(time, anim.track.scale.x, anim.terp),
|
|
54
|
+
resolveScalar(time, anim.track.scale.y, anim.terp),
|
|
55
|
+
],
|
|
56
|
+
resolveScalar(time, anim.track.rotation, anim.terp),
|
|
57
|
+
])
|
|
58
|
+
|
|
59
|
+
export const resolveScalarAnimation =(
|
|
60
|
+
time: number,
|
|
61
|
+
anim: ScalarAnimation,
|
|
62
|
+
): number => resolveScalar(time, anim.track, anim.terp)
|
|
63
|
+
|
|
64
|
+
export const resolveSpatialTransform =(
|
|
65
|
+
spatial: Item.Spatial | Item.AnimatedSpatial,
|
|
66
|
+
time: number,
|
|
67
|
+
): Transform =>
|
|
68
|
+
spatial.kind === Kind.AnimatedSpatial
|
|
69
|
+
? resolveTransformAnimation(time, spatial.anim)
|
|
70
|
+
: spatial.transform
|
|
71
|
+
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
|
|
2
|
+
import {Interpolation, Keyframes} from "../types.js"
|
|
3
|
+
|
|
4
|
+
type EasingFn = (x: number, a: number, b: number) => number
|
|
5
|
+
|
|
6
|
+
class TerpKeys {
|
|
7
|
+
constructor(
|
|
8
|
+
private keys: Keyframes,
|
|
9
|
+
private index: number,
|
|
10
|
+
) {}
|
|
11
|
+
|
|
12
|
+
near(offset: number): number {
|
|
13
|
+
const next = this.keys[Math.max(0, Math.min(this.keys.length - 1, this.index + offset))]
|
|
14
|
+
return next[1]
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type TerpFn = (x: number, keys: TerpKeys) => number
|
|
19
|
+
|
|
20
|
+
const asTerp = (fn: TerpFn): TerpFn => fn
|
|
21
|
+
|
|
22
|
+
const fromEasingFn = (easing: EasingFn): TerpFn =>
|
|
23
|
+
asTerp((x, keys) => easing(x, keys.near(0), keys.near(1)))
|
|
24
|
+
|
|
25
|
+
const lerp = (x: number, a: number, b: number) => a + (b - a) * x
|
|
26
|
+
|
|
27
|
+
export const terps: Record<Interpolation, TerpFn> = {
|
|
28
|
+
linear: fromEasingFn(lerp),
|
|
29
|
+
|
|
30
|
+
ease: fromEasingFn((x, a, b) => {
|
|
31
|
+
const eased = x < 0.5
|
|
32
|
+
? 4 * x * x * x
|
|
33
|
+
: 1 - Math.pow(-2 * x + 2, 3) / 2
|
|
34
|
+
return lerp(eased, a, b)
|
|
35
|
+
}),
|
|
36
|
+
|
|
37
|
+
easeIn: fromEasingFn((x, a, b) => lerp(x * x * x, a, b)),
|
|
38
|
+
|
|
39
|
+
easeOut: fromEasingFn((x, a, b) => lerp(1 - Math.pow(1 - x, 3), a, b)),
|
|
40
|
+
|
|
41
|
+
bounce: fromEasingFn((x, a, b) => {
|
|
42
|
+
const n1 = 7.5625
|
|
43
|
+
const d1 = 2.75
|
|
44
|
+
const eased = x < 1 / d1
|
|
45
|
+
? n1 * x * x
|
|
46
|
+
: x < 2 / d1
|
|
47
|
+
? n1 * (x - 1.5 / d1) ** 2 + 0.75
|
|
48
|
+
: x < 2.5 / d1
|
|
49
|
+
? n1 * (x - 2.25 / d1) ** 2 + 0.9375
|
|
50
|
+
: n1 * (x - 2.625 / d1) ** 2 + 0.984375
|
|
51
|
+
return lerp(eased, a, b)
|
|
52
|
+
}),
|
|
53
|
+
|
|
54
|
+
catmullRom: asTerp((x, keys) => {
|
|
55
|
+
const p1 = keys.near(-1)
|
|
56
|
+
const p2 = keys.near(0)
|
|
57
|
+
const p3 = keys.near(1)
|
|
58
|
+
const p4 = keys.near(2)
|
|
59
|
+
const x2 = x * x
|
|
60
|
+
const x3 = x2 * x
|
|
61
|
+
return 0.5 * (
|
|
62
|
+
(2 * p2) +
|
|
63
|
+
(-p1 + p3) * x +
|
|
64
|
+
(2 * p1 - 5 * p2 + 4 * p3 - p4) * x2 +
|
|
65
|
+
(-p1 + 3 * p2 - 3 * p3 + p4) * x3
|
|
66
|
+
)
|
|
67
|
+
}),
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export const resolveTerp =(
|
|
71
|
+
terp: Interpolation,
|
|
72
|
+
x: number,
|
|
73
|
+
keys: Keyframes,
|
|
74
|
+
index: number,
|
|
75
|
+
): number => {
|
|
76
|
+
const fn = terps[terp]
|
|
77
|
+
if (!fn)
|
|
78
|
+
throw new Error(`unknown terp "${terp}"`)
|
|
79
|
+
|
|
80
|
+
return fn(x, new TerpKeys(keys, index))
|
|
81
|
+
}
|