@omnimedia/omnitool 1.1.0-78 → 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 +107 -1
- 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/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 +74 -2
- package/s/timeline/sugar/o.ts +95 -2
- 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 +105 -105
- 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/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 +19 -2
- package/x/timeline/sugar/helpers.js +43 -0
- package/x/timeline/sugar/helpers.js.map +1 -1
- package/x/timeline/sugar/o.d.ts +18 -1
- package/x/timeline/sugar/o.js +71 -1
- 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 =>
|
|
@@ -146,6 +146,89 @@ const timeline = omni.timeline(o => {
|
|
|
146
146
|
})
|
|
147
147
|
```
|
|
148
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
|
+
|
|
149
232
|
Worker URL notes:
|
|
150
233
|
- `Driver.setup()` defaults to `/node_modules/@omnimedia/omnitool/x/driver/driver.worker.bundle.min.js`.
|
|
151
234
|
- If you serve the worker from a different location, pass `workerUrl`:
|
|
@@ -228,4 +311,27 @@ omnitool ai "make a 15s promo for tea"
|
|
|
228
311
|
|
|
229
312
|
- smooth seeking
|
|
230
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
|
+
|
|
231
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
|
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
|
+
|
|
@@ -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)
|