@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
package/README.md
CHANGED
|
@@ -81,7 +81,7 @@ const timeline = timeline(
|
|
|
81
81
|
|
|
82
82
|
## 🎛 Filters
|
|
83
83
|
|
|
84
|
-
|
|
84
|
+
Filter application:
|
|
85
85
|
|
|
86
86
|
```ts
|
|
87
87
|
const timeline = omni.timeline(o =>
|
|
@@ -115,14 +115,12 @@ Filter metadata for UI:
|
|
|
115
115
|
|
|
116
116
|
```ts
|
|
117
117
|
import {
|
|
118
|
-
|
|
119
|
-
filterSchemas,
|
|
120
|
-
getFilterDefaultParams
|
|
118
|
+
filters
|
|
121
119
|
} from "@omnimedia/omnitool"
|
|
122
120
|
|
|
123
|
-
const available = Object.entries(
|
|
124
|
-
const
|
|
125
|
-
const
|
|
121
|
+
const available = Object.entries(filters)
|
|
122
|
+
const blur = filters.blur
|
|
123
|
+
const schema = blur.schema
|
|
126
124
|
```
|
|
127
125
|
|
|
128
126
|
## 🧭 Spatial Transforms
|
|
@@ -148,6 +146,89 @@ const timeline = omni.timeline(o => {
|
|
|
148
146
|
})
|
|
149
147
|
```
|
|
150
148
|
|
|
149
|
+
Animated spatial transforms:
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
const timeline = omni.timeline(o => {
|
|
153
|
+
const slideIn = o.animatedSpatial(
|
|
154
|
+
o.anim.transform("linear", [
|
|
155
|
+
[0, o.transform({position: [-400, 0]})],
|
|
156
|
+
[1000, o.transform({position: [0, 0]})],
|
|
157
|
+
])
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
const title = o.text("Lower third", {
|
|
161
|
+
duration: 2000,
|
|
162
|
+
styles: {fill: "white", fontSize: 36}
|
|
163
|
+
})
|
|
164
|
+
o.set(title.id, {spatialId: slideIn.id})
|
|
165
|
+
|
|
166
|
+
return o.stack(
|
|
167
|
+
o.video(clip, {duration: 4000}),
|
|
168
|
+
title
|
|
169
|
+
)
|
|
170
|
+
})
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Animation application:
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
const timeline = omni.timeline(o => {
|
|
177
|
+
const title = o.animate.opacity(
|
|
178
|
+
o.text("Lower third", {
|
|
179
|
+
duration: 2000,
|
|
180
|
+
styles: {fill: "white", fontSize: 36},
|
|
181
|
+
}),
|
|
182
|
+
"easeIn",
|
|
183
|
+
[
|
|
184
|
+
[0, 0],
|
|
185
|
+
[700, 1],
|
|
186
|
+
]
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
return o.stack(
|
|
190
|
+
o.video(clip, {duration: 4000}),
|
|
191
|
+
title
|
|
192
|
+
)
|
|
193
|
+
})
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Reusable animation:
|
|
197
|
+
|
|
198
|
+
```ts
|
|
199
|
+
const timeline = omni.timeline(o => {
|
|
200
|
+
const fadeIn = o.animate.opacity.make("easeIn", [
|
|
201
|
+
[0, 0],
|
|
202
|
+
[700, 1],
|
|
203
|
+
])
|
|
204
|
+
|
|
205
|
+
const title = o.text("Lower third", {
|
|
206
|
+
duration: 2000,
|
|
207
|
+
styles: {fill: "white", fontSize: 36},
|
|
208
|
+
})
|
|
209
|
+
o.set(title.id, {animationId: fadeIn.id})
|
|
210
|
+
|
|
211
|
+
return o.stack(
|
|
212
|
+
o.video(clip, {duration: 4000}),
|
|
213
|
+
title
|
|
214
|
+
)
|
|
215
|
+
})
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Animation registry:
|
|
219
|
+
|
|
220
|
+
```ts
|
|
221
|
+
import {animations} from "@omnimedia/omnitool"
|
|
222
|
+
|
|
223
|
+
Object.entries(animations).forEach(([property, meta]) => {
|
|
224
|
+
console.log(property, meta.value)
|
|
225
|
+
// transform transform
|
|
226
|
+
// opacity scalar
|
|
227
|
+
})
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
This is useful for UI code that needs to list keyframeable properties and choose the right editor shape for each one.
|
|
231
|
+
|
|
151
232
|
Worker URL notes:
|
|
152
233
|
- `Driver.setup()` defaults to `/node_modules/@omnimedia/omnitool/x/driver/driver.worker.bundle.min.js`.
|
|
153
234
|
- If you serve the worker from a different location, pass `workerUrl`:
|
|
@@ -230,4 +311,27 @@ omnitool ai "make a 15s promo for tea"
|
|
|
230
311
|
|
|
231
312
|
- smooth seeking
|
|
232
313
|
- keyframes
|
|
314
|
+
- custom filters, likely via driver-side registration with timeline sugar such as:
|
|
315
|
+
|
|
316
|
+
```ts
|
|
317
|
+
// Register custom filter
|
|
318
|
+
driver.registerFilter({
|
|
319
|
+
type: "vhs",
|
|
320
|
+
make: params => new Filter(/* ... */),
|
|
321
|
+
schema: {
|
|
322
|
+
intensity: {type: "number", min: 0, max: 1, default: 0.5},
|
|
323
|
+
scanlines: {type: "boolean", default: true},
|
|
324
|
+
},
|
|
325
|
+
})
|
|
326
|
+
|
|
327
|
+
// Use custom filter
|
|
328
|
+
const timeline = omni.timeline(o =>
|
|
329
|
+
o.filter.custom(
|
|
330
|
+
"vhs",
|
|
331
|
+
o.video(clip, {duration: 3000}),
|
|
332
|
+
{intensity: 0.8}
|
|
333
|
+
)
|
|
334
|
+
)
|
|
335
|
+
```
|
|
336
|
+
|
|
233
337
|
- server-side, not just browsers
|
package/package.json
CHANGED
|
@@ -6,30 +6,45 @@ export async function TimelineSchemaTest(driver: Driver, file: File) {
|
|
|
6
6
|
const omni = new Omni(driver)
|
|
7
7
|
const {videoA} = await omni.load({videoA: Datafile.make(file)})
|
|
8
8
|
const timeline = omni.timeline(o => {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
|
|
9
|
+
const text = o.text("content", {duration: 3000})
|
|
10
|
+
const fade = o.animate.opacity.make("easeIn", [
|
|
11
|
+
[0, 0],
|
|
12
|
+
[700, 1],
|
|
13
|
+
[2200, 1],
|
|
14
|
+
[3000, 0.35],
|
|
15
|
+
])
|
|
16
|
+
const style = o.textStyle({fill: "green", fontSize: 100})
|
|
17
|
+
const videoSpatial = o.spatial(
|
|
18
|
+
o.transform({
|
|
19
|
+
position: [240, 160],
|
|
20
|
+
scale: [1.4, 1.4],
|
|
21
|
+
rotation: 0.08,
|
|
22
|
+
}),
|
|
23
|
+
[0.15, 0.1, 0.05, 0.2],
|
|
24
|
+
)
|
|
25
|
+
const textSpatial = o.animatedSpatial(
|
|
26
|
+
o.anim.transform("linear", [
|
|
27
|
+
[0, o.transform({position: [-320, 80], scale: [0.7, 0.7]})],
|
|
28
|
+
[1000, o.transform({position: [120, 80], scale: [1, 1]})],
|
|
29
|
+
[2000, o.transform({position: [200, 40], scale: [1.35, 1.35], rotation: 8})],
|
|
30
|
+
[3000, o.transform({position: [320, 0], scale: [1.15, 1.15], rotation: 0})],
|
|
31
|
+
]),
|
|
32
|
+
)
|
|
19
33
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
34
|
+
const video = o.video(videoA, {duration: 3000, start: 1000})
|
|
35
|
+
o.set<Item.Text>(text.id, {styleId: style.id, spatialId: textSpatial.id, animationId: fade.id})
|
|
36
|
+
o.set<Item.Video>(video.id, {spatialId: videoSpatial.id})
|
|
23
37
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
38
|
+
return o.sequence(
|
|
39
|
+
o.stack(
|
|
40
|
+
text,
|
|
41
|
+
video,
|
|
42
|
+
o.audio(videoA, {duration: 1000})
|
|
43
|
+
),
|
|
44
|
+
o.gap(500),
|
|
45
|
+
o.video(videoA, {duration: 7000, start: 5000})
|
|
46
|
+
)
|
|
47
|
+
})
|
|
33
48
|
|
|
34
49
|
return {timeline, omni}
|
|
35
50
|
}
|
|
@@ -88,6 +88,7 @@ export type TextLayer = {
|
|
|
88
88
|
content: string
|
|
89
89
|
style?: TextStyleOptions
|
|
90
90
|
matrix?: Mat6
|
|
91
|
+
alpha?: number
|
|
91
92
|
crop?: Crop
|
|
92
93
|
filters?: FilterSpec[]
|
|
93
94
|
}
|
|
@@ -97,6 +98,7 @@ export type ImageLayer = {
|
|
|
97
98
|
kind: 'image'
|
|
98
99
|
frame: VideoFrame
|
|
99
100
|
matrix?: Mat6
|
|
101
|
+
alpha?: number
|
|
100
102
|
crop?: Crop
|
|
101
103
|
filters?: FilterSpec[]
|
|
102
104
|
}
|
|
@@ -111,6 +111,7 @@ export class Compositor {
|
|
|
111
111
|
) {
|
|
112
112
|
const sprite = this.#findOrCreate<Text>(layer)!
|
|
113
113
|
this.#applyTransform(sprite, layer.matrix)
|
|
114
|
+
this.#applyAlpha(sprite, layer.alpha)
|
|
114
115
|
this.#applyCrop(sprite, layer.crop)
|
|
115
116
|
this.#applyFilters(sprite, layer.filters)
|
|
116
117
|
parent.addChild(sprite)
|
|
@@ -132,6 +133,7 @@ export class Compositor {
|
|
|
132
133
|
const texture = Texture.from(layer.frame)
|
|
133
134
|
sprite.texture = texture
|
|
134
135
|
this.#applyTransform(sprite, layer.matrix)
|
|
136
|
+
this.#applyAlpha(sprite, layer.alpha)
|
|
135
137
|
this.#applyCrop(sprite, layer.crop)
|
|
136
138
|
this.#applyFilters(sprite, layer.filters)
|
|
137
139
|
parent.addChild(sprite)
|
|
@@ -166,6 +168,10 @@ export class Compositor {
|
|
|
166
168
|
target.setFromMatrix(mx)
|
|
167
169
|
}
|
|
168
170
|
|
|
171
|
+
#applyAlpha(target: Container, alpha?: number) {
|
|
172
|
+
target.alpha = alpha ?? 1
|
|
173
|
+
}
|
|
174
|
+
|
|
169
175
|
#applyCrop(target: Container, crop?: Crop) {
|
|
170
176
|
const existing = this.#cropMasks.get(target)
|
|
171
177
|
if (existing) {
|
package/s/timeline/index.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
|
|
2
2
|
export * from "./parts/basics.js"
|
|
3
|
+
export * from "./parts/animations.js"
|
|
3
4
|
export * from "./parts/filters.js"
|
|
4
5
|
export * from "./parts/item.js"
|
|
5
6
|
export * from "./parts/media.js"
|
|
6
7
|
export * from "./parts/resource-pool.js"
|
|
7
8
|
export * from "./parts/resource.js"
|
|
8
9
|
export * from "./parts/filmstrip.js"
|
|
10
|
+
export * from "./types.js"
|
|
9
11
|
|
|
10
12
|
export * from "./parts/waveform/waveform.js"
|
|
11
13
|
export * from "./parts/waveform/parts/types.js"
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export type AnimationValue = "scalar" | "transform"
|
|
2
|
+
|
|
3
|
+
export type AnimationDefinition = {
|
|
4
|
+
value: AnimationValue
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const spatialAnimations = {
|
|
8
|
+
transform: {value: "transform"},
|
|
9
|
+
} as const satisfies Record<string, AnimationDefinition>
|
|
10
|
+
|
|
11
|
+
export const visualAnimations = {
|
|
12
|
+
opacity: {value: "scalar"},
|
|
13
|
+
} as const satisfies Record<string, AnimationDefinition>
|
|
14
|
+
|
|
15
|
+
// const audioAnimations = {}
|
|
16
|
+
|
|
17
|
+
export const animations = {
|
|
18
|
+
...spatialAnimations,
|
|
19
|
+
...visualAnimations,
|
|
20
|
+
} as const
|
|
21
|
+
|
|
22
|
+
export type SpatialAnimationProperty = keyof typeof spatialAnimations
|
|
23
|
+
export type VisualAnimationProperty = keyof typeof visualAnimations
|
|
24
|
+
export type AnimationProperty = keyof typeof animations
|
|
@@ -127,7 +127,7 @@ const defineFilter = <TParams>() =>
|
|
|
127
127
|
schema: SchemaFromOptions<TParams> = {},
|
|
128
128
|
): FilterDefinition<TType, TParams> => ({type, schema})
|
|
129
129
|
|
|
130
|
-
export const
|
|
130
|
+
export const filters = {
|
|
131
131
|
adjustment: defineFilter<PixiFilters.AdjustmentFilterOptions>()("AdjustmentFilter", {
|
|
132
132
|
gamma: num(0, 5, 1),
|
|
133
133
|
saturation: num(0, 5, 1),
|
|
@@ -430,7 +430,7 @@ export const filterTypes = {
|
|
|
430
430
|
}),
|
|
431
431
|
} as const
|
|
432
432
|
|
|
433
|
-
type FilterDefinitions = typeof
|
|
433
|
+
type FilterDefinitions = typeof filters
|
|
434
434
|
type FilterDefinitionParams<T> =
|
|
435
435
|
T extends FilterDefinition<string, infer TParams>
|
|
436
436
|
? TParams
|
|
@@ -443,42 +443,11 @@ export type FilterOptions = {
|
|
|
443
443
|
|
|
444
444
|
export type FilterType = FilterDefinitions[keyof FilterDefinitions]["type"]
|
|
445
445
|
export type FilterParams<T extends FilterType = FilterType> = FilterOptions[T]
|
|
446
|
-
export type FilterSchemas = {
|
|
447
|
-
[TName in keyof FilterDefinitions as FilterDefinitions[TName]["type"]]:
|
|
448
|
-
FilterDefinitions[TName]["schema"]
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
export const filterSchemas = Object.fromEntries(
|
|
452
|
-
Object.values(filterTypes).map(filter => [filter.type, filter.schema]),
|
|
453
|
-
) as FilterSchemas
|
|
454
|
-
|
|
455
|
-
const getDefaultValue =(config: FilterPropertyConfig): unknown => {
|
|
456
|
-
switch(config.type) {
|
|
457
|
-
case "number":
|
|
458
|
-
case "boolean":
|
|
459
|
-
case "color":
|
|
460
|
-
case "choice":
|
|
461
|
-
return config.default
|
|
462
|
-
|
|
463
|
-
case "object":
|
|
464
|
-
return Object.fromEntries(
|
|
465
|
-
Object.entries(config.properties).map(([key, value]) => [key, getDefaultValue(value)]),
|
|
466
|
-
)
|
|
467
|
-
|
|
468
|
-
case "array":
|
|
469
|
-
return config.items.map(getDefaultValue)
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
export const getFilterDefaultParams = <TFilter extends FilterType>(type: TFilter): FilterParams<TFilter> =>
|
|
474
|
-
Object.fromEntries(
|
|
475
|
-
Object.entries(filterSchemas[type]).map(([key, value]) => [key, getDefaultValue(value)]),
|
|
476
|
-
) as FilterParams<TFilter>
|
|
477
446
|
|
|
478
447
|
export interface FilterAction<TFilter extends FilterType> {
|
|
479
448
|
<T extends FilterableItem>(item: T, params?: FilterParams<TFilter>): T
|
|
480
449
|
make(params?: FilterParams<TFilter>): Item.Filter<TFilter>
|
|
481
450
|
}
|
|
482
451
|
export type FilterActions = {
|
|
483
|
-
[TName in keyof typeof
|
|
452
|
+
[TName in keyof typeof filters]: FilterAction<(typeof filters)[TName]["type"]>
|
|
484
453
|
}
|
package/s/timeline/parts/item.ts
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
import {TextStyleOptions} from "pixi.js"
|
|
3
3
|
|
|
4
4
|
import {Id, Hash} from "./basics.js"
|
|
5
|
-
import {Transform} from "../types.js"
|
|
6
5
|
import {Ms} from "../../units/ms.js"
|
|
7
6
|
import type {FilterParams, FilterType} from "./filters.js"
|
|
7
|
+
import {Anim, TrackTransform, Transform, VisualAnimations} from "../types.js"
|
|
8
8
|
|
|
9
9
|
export type Crop = [top: number, right: number, bottom: number, left: number]
|
|
10
10
|
|
|
@@ -16,6 +16,8 @@ export enum Kind {
|
|
|
16
16
|
Text,
|
|
17
17
|
Gap,
|
|
18
18
|
Spatial,
|
|
19
|
+
AnimatedSpatial,
|
|
20
|
+
Animation,
|
|
19
21
|
Transition,
|
|
20
22
|
TextStyle,
|
|
21
23
|
Filter
|
|
@@ -40,6 +42,21 @@ export namespace Item {
|
|
|
40
42
|
enabled: boolean
|
|
41
43
|
}
|
|
42
44
|
|
|
45
|
+
export type AnimatedSpatial = {
|
|
46
|
+
id: Id
|
|
47
|
+
kind: Kind.AnimatedSpatial
|
|
48
|
+
anim: Anim<TrackTransform>
|
|
49
|
+
crop?: Crop
|
|
50
|
+
enabled: boolean
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export type Animation = {
|
|
54
|
+
id: Id
|
|
55
|
+
kind: Kind.Animation
|
|
56
|
+
anims: VisualAnimations
|
|
57
|
+
enabled: boolean
|
|
58
|
+
}
|
|
59
|
+
|
|
43
60
|
export type Filter<T extends FilterType = FilterType> = {
|
|
44
61
|
id: Id
|
|
45
62
|
kind: Kind.Filter
|
|
@@ -77,6 +94,7 @@ export namespace Item {
|
|
|
77
94
|
start: number
|
|
78
95
|
duration: number
|
|
79
96
|
spatialId?: Id
|
|
97
|
+
animationId?: Id
|
|
80
98
|
filterIds?: Id[]
|
|
81
99
|
}
|
|
82
100
|
|
|
@@ -95,6 +113,7 @@ export namespace Item {
|
|
|
95
113
|
content: string
|
|
96
114
|
duration: number
|
|
97
115
|
spatialId?: Id
|
|
116
|
+
animationId?: Id
|
|
98
117
|
styleId?: Id
|
|
99
118
|
filterIds?: Id[]
|
|
100
119
|
}
|
|
@@ -115,6 +134,8 @@ export namespace Item {
|
|
|
115
134
|
| Gap
|
|
116
135
|
| Transition
|
|
117
136
|
| Spatial
|
|
137
|
+
| AnimatedSpatial
|
|
138
|
+
| Animation
|
|
118
139
|
| TextStyle
|
|
119
140
|
| Filter
|
|
120
141
|
)
|
|
@@ -123,6 +144,8 @@ export namespace Item {
|
|
|
123
144
|
export type ContainerItem = Item.Sequence | Item.Stack
|
|
124
145
|
export type NonContainerItem = Exclude<Item.Any, ContainerItem>
|
|
125
146
|
export type FilterableItem = Item.Sequence | Item.Stack | Item.Video | Item.Text
|
|
147
|
+
export type SpatialItem = Item.Spatial | Item.AnimatedSpatial
|
|
148
|
+
export type VisualAnimatableItem = Item.Video | Item.Text
|
|
126
149
|
|
|
127
150
|
export type PlayableItem = Item.Any & {
|
|
128
151
|
start: Ms
|
|
@@ -1,26 +1,28 @@
|
|
|
1
1
|
|
|
2
2
|
import {ms, Ms} from '../../../units/ms.js'
|
|
3
3
|
import {Id, TimelineFile} from '../../parts/basics.js'
|
|
4
|
+
import { SampleContext } from './samplers/visual/parts/types.js'
|
|
4
5
|
import {I6, Mat6, mul6, transformToMat6} from '../../utils/matrix.js'
|
|
5
|
-
import {
|
|
6
|
+
import {resolveScalarAnimation, resolveSpatialTransform} from '../../utils/anim.js'
|
|
7
|
+
import {ContainerItem, Item, Kind, PlayableItem, SpatialItem} from '../../parts/item.js'
|
|
6
8
|
|
|
7
9
|
function isPlayableItem(item: Item.Any): item is PlayableItem {
|
|
8
10
|
return 'duration' in item
|
|
9
11
|
}
|
|
10
12
|
|
|
11
13
|
type WalkAtCallbacks = {
|
|
12
|
-
sequence: (x: Item.Sequence, localTime: Ms, ancestors:
|
|
13
|
-
stack: (x: Item.Stack, localTime: Ms, ancestors:
|
|
14
|
-
video: (x: Item.Video, localTime: Ms, ancestors:
|
|
15
|
-
text: (x: Item.Text, localTime: Ms, ancestors:
|
|
16
|
-
audio: (x: Item.Audio, localTime: Ms, ancestors:
|
|
14
|
+
sequence: (x: Item.Sequence, localTime: Ms, ancestors: AncestorAt[]) => void
|
|
15
|
+
stack: (x: Item.Stack, localTime: Ms, ancestors: AncestorAt[]) => void
|
|
16
|
+
video: (x: Item.Video, localTime: Ms, ancestors: AncestorAt[]) => void
|
|
17
|
+
text: (x: Item.Text, localTime: Ms, ancestors: AncestorAt[]) => void
|
|
18
|
+
audio: (x: Item.Audio, localTime: Ms, ancestors: AncestorAt[]) => void
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
type WalkCallbacks = {
|
|
20
|
-
sequence?: (x: Item.Sequence, matrix: Mat6, ancestors:
|
|
21
|
-
stack?: (x: Item.Stack, matrix: Mat6, ancestors:
|
|
22
|
-
video?: (x: Item.Video, matrix: Mat6, ancestors:
|
|
23
|
-
text?: (x: Item.Text, matrix: Mat6, ancestors:
|
|
22
|
+
sequence?: (x: Item.Sequence, matrix: Mat6, ancestors: AncestorAt[]) => void
|
|
23
|
+
stack?: (x: Item.Stack, matrix: Mat6, ancestors: AncestorAt[]) => void
|
|
24
|
+
video?: (x: Item.Video, matrix: Mat6, ancestors: AncestorAt[]) => void
|
|
25
|
+
text?: (x: Item.Text, matrix: Mat6, ancestors: AncestorAt[]) => void
|
|
24
26
|
audio?: (x: Item.Audio) => void
|
|
25
27
|
}
|
|
26
28
|
|
|
@@ -29,10 +31,15 @@ interface Props {
|
|
|
29
31
|
timecode: Ms
|
|
30
32
|
}
|
|
31
33
|
|
|
34
|
+
export interface AncestorAt {
|
|
35
|
+
item: ContainerItem
|
|
36
|
+
localTime: Ms
|
|
37
|
+
}
|
|
38
|
+
|
|
32
39
|
interface At {
|
|
33
40
|
item: Item.Any
|
|
34
41
|
localTime: Ms
|
|
35
|
-
ancestors:
|
|
42
|
+
ancestors: AncestorAt[]
|
|
36
43
|
}
|
|
37
44
|
|
|
38
45
|
export function itemsAt(p: Props): At[] {
|
|
@@ -72,27 +79,29 @@ export function itemsFrom(p: FromProps): At[] {
|
|
|
72
79
|
|
|
73
80
|
export function computeWorldMatrix(
|
|
74
81
|
items: Map<Id, Item.Any>,
|
|
75
|
-
ancestors:
|
|
76
|
-
item: Item.Any
|
|
82
|
+
ancestors: AncestorAt[],
|
|
83
|
+
item: Item.Any,
|
|
84
|
+
localTime: Ms,
|
|
77
85
|
): Mat6 {
|
|
78
86
|
let world = I6
|
|
79
87
|
|
|
80
88
|
for (const ancestor of ancestors) {
|
|
81
|
-
world = applySpatialIfAny(items, ancestor, world)
|
|
89
|
+
world = applySpatialIfAny(items, ancestor.item, world, ancestor.localTime)
|
|
82
90
|
}
|
|
83
91
|
|
|
84
|
-
return applySpatialIfAny(items, item, world)
|
|
92
|
+
return applySpatialIfAny(items, item, world, localTime)
|
|
85
93
|
}
|
|
86
94
|
|
|
87
95
|
function applySpatialIfAny(
|
|
88
96
|
items: Map<Id, Item.Any>,
|
|
89
97
|
item: Item.Any,
|
|
90
|
-
parentMatrix: Mat6
|
|
98
|
+
parentMatrix: Mat6,
|
|
99
|
+
time: Ms,
|
|
91
100
|
) {
|
|
92
101
|
if ("spatialId" in item && item.spatialId) {
|
|
93
|
-
const spatial = items.get(item.spatialId) as
|
|
102
|
+
const spatial = items.get(item.spatialId) as SpatialItem | undefined
|
|
94
103
|
if (spatial?.enabled) {
|
|
95
|
-
const local = transformToMat6(spatial
|
|
104
|
+
const local = transformToMat6(resolveSpatialTransform(spatial, time))
|
|
96
105
|
return mul6(local, parentMatrix)
|
|
97
106
|
}
|
|
98
107
|
}
|
|
@@ -103,27 +112,20 @@ export function walk(
|
|
|
103
112
|
id: Id,
|
|
104
113
|
items: Map<Id, Item.Any>,
|
|
105
114
|
parentMatrix: Mat6,
|
|
115
|
+
localTime: Ms,
|
|
106
116
|
callbacks: WalkCallbacks,
|
|
107
|
-
ancestors:
|
|
117
|
+
ancestors: AncestorAt[] = []
|
|
108
118
|
) {
|
|
109
119
|
const item = items.get(id)
|
|
110
120
|
if (!item) return
|
|
111
121
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
if ("spatialId" in item && item.spatialId) {
|
|
115
|
-
const spatial = items.get(item.spatialId) as Item.Spatial
|
|
116
|
-
if (spatial.enabled) {
|
|
117
|
-
const local = transformToMat6(spatial.transform)
|
|
118
|
-
currentMatrix = mul6(local, currentMatrix)
|
|
119
|
-
}
|
|
120
|
-
}
|
|
122
|
+
const currentMatrix = applySpatialIfAny(items, item, parentMatrix, localTime)
|
|
121
123
|
|
|
122
124
|
switch (item.kind) {
|
|
123
125
|
case Kind.Stack:
|
|
124
126
|
callbacks.stack?.(item, currentMatrix, ancestors)
|
|
125
127
|
for (const childId of item.childrenIds) {
|
|
126
|
-
walk(childId, items, currentMatrix, callbacks, [...ancestors, item])
|
|
128
|
+
walk(childId, items, currentMatrix, localTime, callbacks, [...ancestors, {item, localTime}])
|
|
127
129
|
}
|
|
128
130
|
break
|
|
129
131
|
|
|
@@ -143,8 +145,9 @@ export function walk(
|
|
|
143
145
|
childId,
|
|
144
146
|
items,
|
|
145
147
|
currentMatrix,
|
|
148
|
+
localTime,
|
|
146
149
|
callbacks,
|
|
147
|
-
[...ancestors, item]
|
|
150
|
+
[...ancestors, {item, localTime}]
|
|
148
151
|
)
|
|
149
152
|
}
|
|
150
153
|
|
|
@@ -171,7 +174,7 @@ function walkAt(
|
|
|
171
174
|
items: Map<Id, Item.Any>,
|
|
172
175
|
time: Ms,
|
|
173
176
|
callbacks: WalkAtCallbacks,
|
|
174
|
-
ancestors:
|
|
177
|
+
ancestors: AncestorAt[] = []
|
|
175
178
|
) {
|
|
176
179
|
const item = items.get(id)
|
|
177
180
|
if (!item) return
|
|
@@ -180,7 +183,7 @@ function walkAt(
|
|
|
180
183
|
case Kind.Stack:
|
|
181
184
|
callbacks.stack(item, time, ancestors)
|
|
182
185
|
for (const childId of item.childrenIds) {
|
|
183
|
-
walkAt(childId, items, time, callbacks, [...ancestors, item])
|
|
186
|
+
walkAt(childId, items, time, callbacks, [...ancestors, {item, localTime: time}])
|
|
184
187
|
}
|
|
185
188
|
break
|
|
186
189
|
|
|
@@ -205,7 +208,7 @@ function walkAt(
|
|
|
205
208
|
items,
|
|
206
209
|
localTime,
|
|
207
210
|
callbacks,
|
|
208
|
-
[...ancestors, item]
|
|
211
|
+
[...ancestors, {item, localTime: time}]
|
|
209
212
|
)
|
|
210
213
|
break
|
|
211
214
|
}
|
|
@@ -235,7 +238,7 @@ function walkFrom(
|
|
|
235
238
|
items: Map<Id, Item.Any>,
|
|
236
239
|
from: Ms,
|
|
237
240
|
callbacks: WalkAtCallbacks,
|
|
238
|
-
ancestors:
|
|
241
|
+
ancestors: AncestorAt[] = []
|
|
239
242
|
) {
|
|
240
243
|
const item = items.get(id)
|
|
241
244
|
if (!item) return
|
|
@@ -244,7 +247,7 @@ function walkFrom(
|
|
|
244
247
|
case Kind.Stack:
|
|
245
248
|
callbacks.stack(item, from, ancestors)
|
|
246
249
|
for (const childId of item.childrenIds) {
|
|
247
|
-
walkFrom(childId, items, from, callbacks, [...ancestors, item])
|
|
250
|
+
walkFrom(childId, items, from, callbacks, [...ancestors, {item, localTime: from}])
|
|
248
251
|
}
|
|
249
252
|
break
|
|
250
253
|
|
|
@@ -274,7 +277,7 @@ function walkFrom(
|
|
|
274
277
|
items,
|
|
275
278
|
localTime,
|
|
276
279
|
callbacks,
|
|
277
|
-
[...ancestors, item]
|
|
280
|
+
[...ancestors, {item, localTime: from}]
|
|
278
281
|
)
|
|
279
282
|
|
|
280
283
|
offset = end
|
|
@@ -358,3 +361,17 @@ export function computeItemDuration(
|
|
|
358
361
|
}
|
|
359
362
|
}
|
|
360
363
|
|
|
364
|
+
export function computeOpacity(
|
|
365
|
+
ctx: SampleContext,
|
|
366
|
+
item: Item.Any,
|
|
367
|
+
time: Ms,
|
|
368
|
+
) {
|
|
369
|
+
if (!("animationId" in item) || item.animationId === undefined)
|
|
370
|
+
return 1
|
|
371
|
+
|
|
372
|
+
const animation = ctx.items.get(item.animationId) as Item.Animation | undefined
|
|
373
|
+
return animation?.enabled && animation.anims.opacity
|
|
374
|
+
? resolveScalarAnimation(time, animation.anims.opacity)
|
|
375
|
+
: 1
|
|
376
|
+
}
|
|
377
|
+
|