@lovelace_lol/polymer 0.0.1
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 +226 -0
- package/dist/index.d.ts +2603 -0
- package/dist/index.js +1031 -0
- package/dist/test.cjs +1519 -0
- package/dist/worker/agency-worker.js +972 -0
- package/dist/worker/manifest.edn +1 -0
- package/package.json +57 -0
- package/shadow-cljs.edn +173 -0
- package/src/polymer/animation/agency.cljs +84 -0
- package/src/polymer/animation/domain.cljs +110 -0
- package/src/polymer/animation/planner.cljs +88 -0
- package/src/polymer/animation/runtime.cljs +137 -0
- package/src/polymer/animation/scheduler.cljs +178 -0
- package/src/polymer/animation/service.cljs +981 -0
- package/src/polymer/animation/state.cljs +112 -0
- package/src/polymer/blink/agency.cljs +131 -0
- package/src/polymer/blink/planner.cljs +115 -0
- package/src/polymer/blink/scheduler.cljs +90 -0
- package/src/polymer/blink/snippet.cljs +50 -0
- package/src/polymer/blink/state.cljs +102 -0
- package/src/polymer/camera_context/agency.cljs +132 -0
- package/src/polymer/camera_context/domain.cljs +186 -0
- package/src/polymer/camera_context/planner.cljs +50 -0
- package/src/polymer/camera_context/scheduler.cljs +113 -0
- package/src/polymer/camera_context/state.cljs +121 -0
- package/src/polymer/character.cljs +471 -0
- package/src/polymer/conversation/agency.cljs +83 -0
- package/src/polymer/conversation/domain.cljs +35 -0
- package/src/polymer/conversation/effector.cljs +130 -0
- package/src/polymer/conversation/planner.cljs +178 -0
- package/src/polymer/conversation/scheduler.cljs +202 -0
- package/src/polymer/conversation/service.cljs +329 -0
- package/src/polymer/conversation/state.cljs +186 -0
- package/src/polymer/core.cljs +172 -0
- package/src/polymer/eye_head/agency.cljs +75 -0
- package/src/polymer/eye_head/domain.cljs +133 -0
- package/src/polymer/eye_head/goap.cljs +106 -0
- package/src/polymer/eye_head/scheduler.cljs +176 -0
- package/src/polymer/eye_head/service.cljs +259 -0
- package/src/polymer/eye_head/snippet.cljs +92 -0
- package/src/polymer/eye_head/state.cljs +167 -0
- package/src/polymer/gaze/agency.cljs +85 -0
- package/src/polymer/gaze/domain.cljs +211 -0
- package/src/polymer/gaze/goap.cljs +208 -0
- package/src/polymer/gaze/scheduler.cljs +184 -0
- package/src/polymer/gaze/state.cljs +202 -0
- package/src/polymer/gesture/agency.cljs +185 -0
- package/src/polymer/gesture/goap.cljs +461 -0
- package/src/polymer/gesture/scheduler.cljs +122 -0
- package/src/polymer/gesture/snippet.cljs +198 -0
- package/src/polymer/gesture/state.cljs +273 -0
- package/src/polymer/lipsync/agency.cljs +593 -0
- package/src/polymer/lipsync/articulation/jaw.cljs +258 -0
- package/src/polymer/lipsync/articulation/snippet.cljs +605 -0
- package/src/polymer/lipsync/articulation/tongue.cljs +418 -0
- package/src/polymer/lipsync/articulation/visemes.cljs +429 -0
- package/src/polymer/lipsync/goap.cljs +132 -0
- package/src/polymer/lipsync/pronunciation/effector.cljs +51 -0
- package/src/polymer/lipsync/scheduler.cljs +302 -0
- package/src/polymer/lipsync/state.cljs +202 -0
- package/src/polymer/nlp/phonemes.cljs +164 -0
- package/src/polymer/prosodic/agency.cljs +196 -0
- package/src/polymer/prosodic/goap.cljs +122 -0
- package/src/polymer/prosodic/scheduler.cljs +99 -0
- package/src/polymer/prosodic/snippet.cljs +106 -0
- package/src/polymer/prosodic/state.cljs +139 -0
- package/src/polymer/runtime/embody.cljs +117 -0
- package/src/polymer/runtime/embody_wasm.cljs +177 -0
- package/src/polymer/runtime/rust_embody_host.cljs +645 -0
- package/src/polymer/society.cljs +239 -0
- package/src/polymer/stream.cljs +130 -0
- package/src/polymer/transcription/agency.cljs +69 -0
- package/src/polymer/transcription/domain.cljs +68 -0
- package/src/polymer/transcription/effector.cljs +611 -0
- package/src/polymer/transcription/planner.cljs +203 -0
- package/src/polymer/transcription/scheduler.cljs +225 -0
- package/src/polymer/transcription/service.cljs +209 -0
- package/src/polymer/transcription/state.cljs +239 -0
- package/src/polymer/tts/agency.cljs +503 -0
- package/src/polymer/tts/azure.cljs +290 -0
- package/src/polymer/tts/planner.cljs +178 -0
- package/src/polymer/tts/runtime.cljs +624 -0
- package/src/polymer/tts/scheduler.cljs +162 -0
- package/src/polymer/tts/state.cljs +191 -0
- package/src/polymer/worker.cljs +70 -0
|
@@ -0,0 +1,981 @@
|
|
|
1
|
+
(ns polymer.animation.service
|
|
2
|
+
(:require [clojure.string :as str]
|
|
3
|
+
[polymer.animation.agency :as animation-agency]))
|
|
4
|
+
|
|
5
|
+
;; This namespace is the JavaScript compatibility API for LoomLarge's existing
|
|
6
|
+
;; animation manager calls. It does not reimplement Latticework's runtime path:
|
|
7
|
+
;; snippet playback is normalized here as data and then dispatched into
|
|
8
|
+
;; Polymer's CLJS Animation agency, whose scheduler owns the Embody side effect.
|
|
9
|
+
|
|
10
|
+
(def snippet-categories
|
|
11
|
+
#{"emotionAnimationsList"
|
|
12
|
+
"speakingAnimationsList"
|
|
13
|
+
"visemeAnimationsList"
|
|
14
|
+
"eyeHeadTrackingAnimationsList"})
|
|
15
|
+
|
|
16
|
+
(def bundled-snippet-manifest-key "bundledAnimationSnippetsManifest")
|
|
17
|
+
(def bundled-snippet-version-key "bundledAnimationSnippetsVersion")
|
|
18
|
+
|
|
19
|
+
(defn now-ms []
|
|
20
|
+
(let [performance (aget js/globalThis "performance")]
|
|
21
|
+
(if (and performance (fn? (aget performance "now")))
|
|
22
|
+
(.now performance)
|
|
23
|
+
(.now js/Date))))
|
|
24
|
+
|
|
25
|
+
(defn finite-number? [value]
|
|
26
|
+
(and (number? value) (js/isFinite value)))
|
|
27
|
+
|
|
28
|
+
(defn number-or [value fallback]
|
|
29
|
+
(if (finite-number? value) value fallback))
|
|
30
|
+
|
|
31
|
+
(defn string-or [value fallback]
|
|
32
|
+
(if (and (string? value) (not (str/blank? value))) value fallback))
|
|
33
|
+
|
|
34
|
+
(defn key->string [value]
|
|
35
|
+
(cond
|
|
36
|
+
(keyword? value) (name value)
|
|
37
|
+
(string? value) value
|
|
38
|
+
(nil? value) ""
|
|
39
|
+
:else (str value)))
|
|
40
|
+
|
|
41
|
+
(defn value->clj [value]
|
|
42
|
+
(cond
|
|
43
|
+
(nil? value) nil
|
|
44
|
+
(map? value) value
|
|
45
|
+
:else (js->clj value :keywordize-keys true)))
|
|
46
|
+
|
|
47
|
+
(defn js-call [target method-name & args]
|
|
48
|
+
(when target
|
|
49
|
+
(when-let [method (aget target method-name)]
|
|
50
|
+
(when (fn? method)
|
|
51
|
+
(.apply method target (to-array args))))))
|
|
52
|
+
|
|
53
|
+
(defn subscription [unsubscribe]
|
|
54
|
+
#js {:unsubscribe unsubscribe})
|
|
55
|
+
|
|
56
|
+
(defn subscribe! [listeners listener]
|
|
57
|
+
(swap! listeners conj listener)
|
|
58
|
+
(subscription (fn [] (swap! listeners disj listener))))
|
|
59
|
+
|
|
60
|
+
(defn notify-listener! [listener payload]
|
|
61
|
+
(cond
|
|
62
|
+
(fn? listener)
|
|
63
|
+
(listener payload)
|
|
64
|
+
|
|
65
|
+
(and listener (fn? (aget listener "next")))
|
|
66
|
+
(.call (aget listener "next") listener payload)
|
|
67
|
+
|
|
68
|
+
:else
|
|
69
|
+
(.warn js/console "[Polymer Animation] stream listener has no callback" listener)))
|
|
70
|
+
|
|
71
|
+
(defn emit-to! [listeners payload]
|
|
72
|
+
(let [js-payload (clj->js payload)]
|
|
73
|
+
(doseq [listener @listeners]
|
|
74
|
+
(try
|
|
75
|
+
(notify-listener! listener js-payload)
|
|
76
|
+
(catch :default error
|
|
77
|
+
(.error js/console "[Polymer Animation] stream listener failed" error))))))
|
|
78
|
+
|
|
79
|
+
(defn create-port [listeners]
|
|
80
|
+
(let [port (atom nil)]
|
|
81
|
+
(reset! port
|
|
82
|
+
#js {:subscribe (fn [listener] (subscribe! listeners listener))
|
|
83
|
+
:pipe (fn [& operators]
|
|
84
|
+
;; LoomLarge still uses RxJS operators such as
|
|
85
|
+
;; filter(...). Those operators expect a source with
|
|
86
|
+
;; subscribe(...) and return a derived observable. This
|
|
87
|
+
;; small pipe keeps Polymer's stream as plain data
|
|
88
|
+
;; while preserving the old observable API contract.
|
|
89
|
+
(reduce (fn [source operator]
|
|
90
|
+
(operator source))
|
|
91
|
+
@port
|
|
92
|
+
operators))})
|
|
93
|
+
@port))
|
|
94
|
+
|
|
95
|
+
(defn event-timestamp []
|
|
96
|
+
(now-ms))
|
|
97
|
+
|
|
98
|
+
(defn emit-event! [event-listeners event]
|
|
99
|
+
(emit-to! event-listeners (assoc event :timestamp (event-timestamp))))
|
|
100
|
+
|
|
101
|
+
(defn normalize-point [point]
|
|
102
|
+
(let [source (or (value->clj point) {})
|
|
103
|
+
time (number-or (:time source) (number-or (:t source) 0))
|
|
104
|
+
intensity (number-or (:intensity source) (number-or (:v source) 0))]
|
|
105
|
+
{:time time
|
|
106
|
+
:intensity intensity
|
|
107
|
+
:inherit (boolean (:inherit source))}))
|
|
108
|
+
|
|
109
|
+
(defn normalize-curve-points [points]
|
|
110
|
+
(->> (or points [])
|
|
111
|
+
(map normalize-point)
|
|
112
|
+
(sort-by :time)
|
|
113
|
+
vec))
|
|
114
|
+
|
|
115
|
+
(defn normalize-curves [curves]
|
|
116
|
+
(let [source (or (value->clj curves) {})]
|
|
117
|
+
(reduce-kv
|
|
118
|
+
(fn [out k points]
|
|
119
|
+
(assoc out (key->string k) (normalize-curve-points points)))
|
|
120
|
+
{}
|
|
121
|
+
source)))
|
|
122
|
+
|
|
123
|
+
(defn add-authoring-point [out point key-name]
|
|
124
|
+
(let [source (or (value->clj point) {})
|
|
125
|
+
curve-key (key->string (or (get source key-name) (:id source) (:key source)))]
|
|
126
|
+
(if (str/blank? curve-key)
|
|
127
|
+
out
|
|
128
|
+
(update out curve-key (fnil conj []) (normalize-point source)))))
|
|
129
|
+
|
|
130
|
+
(defn authoring-curves [snippet]
|
|
131
|
+
(let [mapped (-> {}
|
|
132
|
+
(#(reduce (fn [out point] (add-authoring-point out point :id))
|
|
133
|
+
%
|
|
134
|
+
(or (:au snippet) [])))
|
|
135
|
+
(#(reduce (fn [out point] (add-authoring-point out point :key))
|
|
136
|
+
%
|
|
137
|
+
(or (:viseme snippet) []))))]
|
|
138
|
+
(reduce-kv
|
|
139
|
+
(fn [out key points]
|
|
140
|
+
(assoc out key (normalize-curve-points points)))
|
|
141
|
+
{}
|
|
142
|
+
mapped)))
|
|
143
|
+
|
|
144
|
+
(defn curve-duration [curves]
|
|
145
|
+
(reduce
|
|
146
|
+
(fn [max-time points]
|
|
147
|
+
(if-let [last-point (last points)]
|
|
148
|
+
(max max-time (number-or (:time last-point) 0))
|
|
149
|
+
max-time))
|
|
150
|
+
0
|
|
151
|
+
(vals (or curves {}))))
|
|
152
|
+
|
|
153
|
+
(defn channel-duration [channels]
|
|
154
|
+
(reduce
|
|
155
|
+
(fn [max-time channel]
|
|
156
|
+
(reduce
|
|
157
|
+
(fn [inner-max keyframe]
|
|
158
|
+
(max inner-max (number-or (:time keyframe) 0)))
|
|
159
|
+
max-time
|
|
160
|
+
(or (:keyframes channel) [])))
|
|
161
|
+
0
|
|
162
|
+
(or channels [])))
|
|
163
|
+
|
|
164
|
+
(defn normalize-channels [channels]
|
|
165
|
+
(when (seq channels)
|
|
166
|
+
(->> channels
|
|
167
|
+
(map value->clj)
|
|
168
|
+
(mapv (fn [channel]
|
|
169
|
+
(update channel :keyframes normalize-curve-points))))))
|
|
170
|
+
|
|
171
|
+
(defn normalize-loop-mode [snippet]
|
|
172
|
+
(let [mode (:mixerLoopMode snippet)]
|
|
173
|
+
(if (#{"repeat" "once" "pingpong"} mode)
|
|
174
|
+
mode
|
|
175
|
+
(if (:loop snippet) "repeat" "once"))))
|
|
176
|
+
|
|
177
|
+
(defn normalize-snippet [snippet]
|
|
178
|
+
(let [source (or (value->clj snippet) {})
|
|
179
|
+
now (now-ms)
|
|
180
|
+
curves (if (:curves source)
|
|
181
|
+
(normalize-curves (:curves source))
|
|
182
|
+
(authoring-curves source))
|
|
183
|
+
channels (normalize-channels (:channels source))
|
|
184
|
+
duration (max (number-or (:duration source) 0)
|
|
185
|
+
(number-or (:maxTime source) 0)
|
|
186
|
+
(curve-duration curves)
|
|
187
|
+
(channel-duration channels))
|
|
188
|
+
loop-mode (normalize-loop-mode source)
|
|
189
|
+
name (string-or (:name source) (str "polymer:animation:" now))]
|
|
190
|
+
(cond->
|
|
191
|
+
(assoc source
|
|
192
|
+
:name name
|
|
193
|
+
:curves curves
|
|
194
|
+
:isPlaying (boolean (:isPlaying source))
|
|
195
|
+
:loop (not= loop-mode "once")
|
|
196
|
+
:loopIteration (number-or (:loopIteration source) 0)
|
|
197
|
+
:loopDirection (if (= (:loopDirection source) -1) -1 (if (:mixerReverse source) -1 1))
|
|
198
|
+
:lastLoopTime (number-or (:lastLoopTime source) 0)
|
|
199
|
+
:snippetPlaybackRate (number-or (:snippetPlaybackRate source) 1)
|
|
200
|
+
:snippetIntensityScale (number-or (:snippetIntensityScale source) 1)
|
|
201
|
+
:snippetBlendMode (if (= (:snippetBlendMode source) "additive") "additive" "replace")
|
|
202
|
+
:snippetJawScale (number-or (:snippetJawScale source) 1)
|
|
203
|
+
:snippetBalance (max -1 (min 1 (number-or (:snippetBalance source) 0)))
|
|
204
|
+
:snippetBalanceMap (or (:snippetBalanceMap source) {})
|
|
205
|
+
:snippetCategory (string-or (:snippetCategory source) "default")
|
|
206
|
+
:snippetPriority (number-or (:snippetPriority source) 0)
|
|
207
|
+
:snippetEasing (string-or (:snippetEasing source) "linear")
|
|
208
|
+
:mixerLoopMode loop-mode
|
|
209
|
+
:mixerRepeatCount (when (finite-number? (:mixerRepeatCount source))
|
|
210
|
+
(:mixerRepeatCount source))
|
|
211
|
+
:mixerReverse (boolean (:mixerReverse source))
|
|
212
|
+
:currentTime (number-or (:currentTime source) 0)
|
|
213
|
+
:startWallTime (number-or (:startWallTime source) now)
|
|
214
|
+
:duration duration
|
|
215
|
+
:maxTime duration
|
|
216
|
+
:cursor (or (:cursor source) {}))
|
|
217
|
+
channels (assoc :channels channels)
|
|
218
|
+
(contains? source :autoVisemeJaw) (assoc :autoVisemeJaw (boolean (:autoVisemeJaw source))))))
|
|
219
|
+
|
|
220
|
+
(defn snippet-ui-state [snippet]
|
|
221
|
+
(let [loop-mode (normalize-loop-mode snippet)]
|
|
222
|
+
{:name (:name snippet)
|
|
223
|
+
:isPlaying (boolean (:isPlaying snippet))
|
|
224
|
+
:loop (not= loop-mode "once")
|
|
225
|
+
:loopMode loop-mode
|
|
226
|
+
:reverse (boolean (:mixerReverse snippet))
|
|
227
|
+
:repeatCount (:mixerRepeatCount snippet)
|
|
228
|
+
:loopIteration (:loopIteration snippet)
|
|
229
|
+
:loopDirection (:loopDirection snippet)
|
|
230
|
+
:currentTime (number-or (:currentTime snippet) 0)
|
|
231
|
+
:duration (number-or (:duration snippet) 0)
|
|
232
|
+
:playbackRate (number-or (:snippetPlaybackRate snippet) 1)
|
|
233
|
+
:intensityScale (number-or (:snippetIntensityScale snippet) 1)
|
|
234
|
+
:blendMode (if (= (:snippetBlendMode snippet) "additive") "additive" "replace")
|
|
235
|
+
:balance (number-or (:snippetBalance snippet) 0)
|
|
236
|
+
:category (string-or (:snippetCategory snippet) "default")
|
|
237
|
+
:easing (string-or (:snippetEasing snippet) "linear")}))
|
|
238
|
+
|
|
239
|
+
(defn clone-snippet [snippet]
|
|
240
|
+
(assoc snippet
|
|
241
|
+
:curves (reduce-kv (fn [out key points] (assoc out key (mapv identity points))) {} (:curves snippet))
|
|
242
|
+
:snippetBalanceMap (merge {} (:snippetBalanceMap snippet))
|
|
243
|
+
:cursor (merge {} (:cursor snippet))))
|
|
244
|
+
|
|
245
|
+
(defn normalize-baked-clip [clip]
|
|
246
|
+
(let [source (or (value->clj clip) {})]
|
|
247
|
+
{:name (string-or (:name source) "")
|
|
248
|
+
:duration (number-or (:duration source) 0)
|
|
249
|
+
:channels (vec (or (:channels source) []))}))
|
|
250
|
+
|
|
251
|
+
(defn baked-ui-state [clip state]
|
|
252
|
+
(let [clip (normalize-baked-clip clip)
|
|
253
|
+
state (or state {})
|
|
254
|
+
current-time (number-or (:currentTime state) (number-or (:time state) 0))
|
|
255
|
+
playback-rate (number-or (:playbackRate state) (number-or (:speed state) 1))
|
|
256
|
+
intensity-scale (number-or (:intensityScale state) (number-or (:weight state) 1))
|
|
257
|
+
loop-mode (if (#{"repeat" "once" "pingpong"} (:loopMode state))
|
|
258
|
+
(:loopMode state)
|
|
259
|
+
(if (:loop state) "repeat" "once"))
|
|
260
|
+
blend-mode (if (= (:blendMode state) "additive") "additive" "replace")]
|
|
261
|
+
{:name (:name clip)
|
|
262
|
+
:source "baked"
|
|
263
|
+
:time current-time
|
|
264
|
+
:currentTime current-time
|
|
265
|
+
:duration (number-or (:duration state) (:duration clip))
|
|
266
|
+
:speed playback-rate
|
|
267
|
+
:playbackRate playback-rate
|
|
268
|
+
:weight (number-or (:weight state) intensity-scale)
|
|
269
|
+
:intensityScale intensity-scale
|
|
270
|
+
:isPlaying (boolean (:isPlaying state))
|
|
271
|
+
:isPaused (boolean (:isPaused state))
|
|
272
|
+
:loop (if (contains? state :loop) (boolean (:loop state)) (not= loop-mode "once"))
|
|
273
|
+
:loopMode loop-mode
|
|
274
|
+
:reverse (boolean (:reverse state))
|
|
275
|
+
:repeatCount (:repeatCount state)
|
|
276
|
+
:requestedBlendMode (if (= (:requestedBlendMode state) "additive") "additive" blend-mode)
|
|
277
|
+
:blendMode blend-mode
|
|
278
|
+
:balance (max -1 (min 1 (number-or (:balance state) 0)))
|
|
279
|
+
:category "baked"
|
|
280
|
+
:easing (string-or (:easing state) "linear")
|
|
281
|
+
:channels (vec (or (:channels state) (:channels clip) []))}))
|
|
282
|
+
|
|
283
|
+
(defn create-event-system []
|
|
284
|
+
(let [event-listeners (atom #{})
|
|
285
|
+
snippet-list-listeners (atom #{})
|
|
286
|
+
global-state-listeners (atom #{})
|
|
287
|
+
baked-clip-listeners (atom #{})
|
|
288
|
+
playing-baked-listeners (atom #{})
|
|
289
|
+
baked-animation-listeners (atom {})
|
|
290
|
+
snippet-accessor (atom (fn [] []))
|
|
291
|
+
global-state (atom "stopped")
|
|
292
|
+
baked-clips (atom [])
|
|
293
|
+
baked-states (atom {})]
|
|
294
|
+
(letfn [(raw-snippets [] (@snippet-accessor))
|
|
295
|
+
(raw-snippet [name] (first (filter #(= (:name %) name) (raw-snippets))))
|
|
296
|
+
(ui-snippets [] (mapv snippet-ui-state (raw-snippets)))
|
|
297
|
+
(emit-snippet-list! [] (emit-to! snippet-list-listeners (mapv :name (ui-snippets))))
|
|
298
|
+
(get-baked-state [clip-name] (get @baked-states clip-name))
|
|
299
|
+
(emit-baked-state! [clip-name]
|
|
300
|
+
(when-let [listeners (get @baked-animation-listeners clip-name)]
|
|
301
|
+
(emit-to! (atom listeners) (get-baked-state clip-name))))
|
|
302
|
+
(playing-baked []
|
|
303
|
+
(->> (vals @baked-states)
|
|
304
|
+
(filter #(or (:isPlaying %) (:isPaused %)))
|
|
305
|
+
vec))
|
|
306
|
+
(emit-playing-baked! [] (emit-to! playing-baked-listeners (playing-baked)))
|
|
307
|
+
(merge-baked! [clip-name patch]
|
|
308
|
+
(let [clip (or (first (filter #(= (:name %) clip-name) @baked-clips))
|
|
309
|
+
{:name clip-name :duration 0 :channels []})
|
|
310
|
+
current (get-baked-state clip-name)
|
|
311
|
+
next-state (baked-ui-state clip (merge current (or patch {})))]
|
|
312
|
+
(swap! baked-states assoc clip-name next-state)
|
|
313
|
+
next-state))
|
|
314
|
+
(emit-baked-param-change! [clip-name params]
|
|
315
|
+
(emit-event! event-listeners
|
|
316
|
+
{:type "BAKED_ANIMATION_PARAMS_CHANGED"
|
|
317
|
+
:clipName clip-name
|
|
318
|
+
:params params})
|
|
319
|
+
(emit-baked-state! clip-name)
|
|
320
|
+
(emit-playing-baked!))]
|
|
321
|
+
{:events (create-port event-listeners)
|
|
322
|
+
:snippet-list-port (create-port snippet-list-listeners)
|
|
323
|
+
:global-state-port (create-port global-state-listeners)
|
|
324
|
+
:baked-clip-port (create-port baked-clip-listeners)
|
|
325
|
+
:playing-baked-port (create-port playing-baked-listeners)
|
|
326
|
+
:baked-animation-state-port
|
|
327
|
+
(fn [clip-name]
|
|
328
|
+
#js {:subscribe
|
|
329
|
+
(fn [listener]
|
|
330
|
+
(swap! baked-animation-listeners update clip-name (fnil conj #{}) listener)
|
|
331
|
+
(subscription
|
|
332
|
+
(fn []
|
|
333
|
+
(swap! baked-animation-listeners update clip-name disj listener))))})
|
|
334
|
+
:set-baked-state! (fn [clip-name state]
|
|
335
|
+
(let [next-state (baked-ui-state
|
|
336
|
+
(or (first (filter #(= (:name %) clip-name) @baked-clips))
|
|
337
|
+
{:name clip-name :duration 0 :channels []})
|
|
338
|
+
(value->clj state))]
|
|
339
|
+
(swap! baked-states assoc clip-name next-state)
|
|
340
|
+
(emit-baked-state! clip-name)
|
|
341
|
+
(emit-playing-baked!)
|
|
342
|
+
next-state))
|
|
343
|
+
:get-baked-state get-baked-state
|
|
344
|
+
:emitter
|
|
345
|
+
#js {:events (create-port event-listeners)
|
|
346
|
+
:setSnippetAccessor (fn [get-snippets]
|
|
347
|
+
(reset! snippet-accessor
|
|
348
|
+
(fn []
|
|
349
|
+
(mapv normalize-snippet (or (get-snippets) [])))))
|
|
350
|
+
:getSnippets (fn [] (clj->js (ui-snippets)))
|
|
351
|
+
:getRawSnippets (fn [] (clj->js (mapv clone-snippet (raw-snippets))))
|
|
352
|
+
:getRawSnippet (fn [name] (when-let [snippet (raw-snippet name)] (clj->js (clone-snippet snippet))))
|
|
353
|
+
:getSnippet (fn [name] (when-let [snippet (raw-snippet name)] (clj->js (snippet-ui-state snippet))))
|
|
354
|
+
:getGlobalState (fn [] @global-state)
|
|
355
|
+
:emitSnippetAdded (fn [snippet-name]
|
|
356
|
+
(emit-event! event-listeners {:type "SNIPPET_ADDED" :snippetName snippet-name})
|
|
357
|
+
(emit-snippet-list!))
|
|
358
|
+
:emitSnippetRemoved (fn [snippet-name]
|
|
359
|
+
(emit-event! event-listeners {:type "SNIPPET_REMOVED" :snippetName snippet-name})
|
|
360
|
+
(emit-snippet-list!))
|
|
361
|
+
:emitPlayStateChanged (fn [snippet-name playing?]
|
|
362
|
+
(emit-event! event-listeners
|
|
363
|
+
{:type "SNIPPET_PLAY_STATE_CHANGED"
|
|
364
|
+
:snippetName snippet-name
|
|
365
|
+
:isPlaying (boolean playing?)}))
|
|
366
|
+
:emitSnippetUpdated (fn [snippet-name]
|
|
367
|
+
(emit-event! event-listeners {:type "SNIPPET_UPDATED" :snippetName snippet-name}))
|
|
368
|
+
:emitSnippetLooped (fn [data]
|
|
369
|
+
(emit-event! event-listeners
|
|
370
|
+
(merge {:type "SNIPPET_LOOPED"} (or (value->clj data) {}))))
|
|
371
|
+
:emitSnippetCompleted (fn [snippet-name]
|
|
372
|
+
(emit-event! event-listeners {:type "SNIPPET_COMPLETED" :snippetName snippet-name})
|
|
373
|
+
(emit-event! event-listeners
|
|
374
|
+
{:type "SNIPPET_PLAY_STATE_CHANGED"
|
|
375
|
+
:snippetName snippet-name
|
|
376
|
+
:isPlaying false}))
|
|
377
|
+
:emitKeyframeCompleted (fn [data]
|
|
378
|
+
(emit-event! event-listeners
|
|
379
|
+
(merge {:type "KEYFRAME_COMPLETED"} (or (value->clj data) {}))))
|
|
380
|
+
:emitGlobalPlaybackChanged (fn [state]
|
|
381
|
+
(reset! global-state state)
|
|
382
|
+
(emit-event! event-listeners
|
|
383
|
+
{:type "GLOBAL_PLAYBACK_CHANGED" :state state})
|
|
384
|
+
(emit-to! global-state-listeners state))
|
|
385
|
+
:emitSnippetSeeked (fn [snippet-name time]
|
|
386
|
+
(emit-event! event-listeners
|
|
387
|
+
{:type "SNIPPET_SEEKED"
|
|
388
|
+
:snippetName snippet-name
|
|
389
|
+
:time time}))
|
|
390
|
+
:emitParamsChanged (fn [snippet-name params]
|
|
391
|
+
(emit-event! event-listeners
|
|
392
|
+
{:type "SNIPPET_PARAMS_CHANGED"
|
|
393
|
+
:snippetName snippet-name
|
|
394
|
+
:params (or (value->clj params) {})}))
|
|
395
|
+
:emitBakedClipsLoaded (fn [clips]
|
|
396
|
+
(let [next-clips (mapv normalize-baked-clip (or (value->clj clips) []))
|
|
397
|
+
next-states (reduce
|
|
398
|
+
(fn [out clip]
|
|
399
|
+
(assoc out (:name clip)
|
|
400
|
+
(baked-ui-state clip (get @baked-states (:name clip)))))
|
|
401
|
+
{}
|
|
402
|
+
next-clips)]
|
|
403
|
+
(reset! baked-clips next-clips)
|
|
404
|
+
(reset! baked-states next-states)
|
|
405
|
+
(emit-event! event-listeners
|
|
406
|
+
{:type "BAKED_CLIPS_LOADED" :clips next-clips})
|
|
407
|
+
(emit-to! baked-clip-listeners next-clips)
|
|
408
|
+
(emit-playing-baked!)
|
|
409
|
+
(doseq [clip next-clips]
|
|
410
|
+
(emit-baked-state! (:name clip)))))
|
|
411
|
+
:emitBakedAnimationStarted (fn [clip-name state]
|
|
412
|
+
(let [next-state (merge-baked! clip-name
|
|
413
|
+
(assoc (or (value->clj state) {})
|
|
414
|
+
:isPlaying true
|
|
415
|
+
:isPaused false))]
|
|
416
|
+
(emit-event! event-listeners
|
|
417
|
+
{:type "BAKED_ANIMATION_STARTED"
|
|
418
|
+
:clipName clip-name
|
|
419
|
+
:state next-state})
|
|
420
|
+
(emit-baked-state! clip-name)
|
|
421
|
+
(emit-playing-baked!)))
|
|
422
|
+
:emitBakedAnimationStopped (fn [clip-name]
|
|
423
|
+
(merge-baked! clip-name {:isPlaying false :isPaused false :time 0 :currentTime 0})
|
|
424
|
+
(emit-event! event-listeners
|
|
425
|
+
{:type "BAKED_ANIMATION_STOPPED" :clipName clip-name})
|
|
426
|
+
(emit-baked-state! clip-name)
|
|
427
|
+
(emit-playing-baked!))
|
|
428
|
+
:emitBakedAnimationPaused (fn [clip-name]
|
|
429
|
+
(merge-baked! clip-name {:isPlaying false :isPaused true})
|
|
430
|
+
(emit-event! event-listeners
|
|
431
|
+
{:type "BAKED_ANIMATION_PAUSED" :clipName clip-name})
|
|
432
|
+
(emit-baked-state! clip-name)
|
|
433
|
+
(emit-playing-baked!))
|
|
434
|
+
:emitBakedAnimationResumed (fn [clip-name]
|
|
435
|
+
(merge-baked! clip-name {:isPlaying true :isPaused false})
|
|
436
|
+
(emit-event! event-listeners
|
|
437
|
+
{:type "BAKED_ANIMATION_RESUMED" :clipName clip-name})
|
|
438
|
+
(emit-baked-state! clip-name)
|
|
439
|
+
(emit-playing-baked!))
|
|
440
|
+
:emitBakedAnimationCompleted (fn [clip-name]
|
|
441
|
+
(let [current (merge-baked! clip-name {})
|
|
442
|
+
terminal-time (if (:reverse current) 0 (:duration current))]
|
|
443
|
+
(merge-baked! clip-name {:isPlaying false
|
|
444
|
+
:isPaused false
|
|
445
|
+
:time terminal-time
|
|
446
|
+
:currentTime terminal-time})
|
|
447
|
+
(emit-event! event-listeners
|
|
448
|
+
{:type "BAKED_ANIMATION_COMPLETED" :clipName clip-name})
|
|
449
|
+
(emit-baked-state! clip-name)
|
|
450
|
+
(emit-playing-baked!)))
|
|
451
|
+
:emitBakedAnimationProgress (fn [clip-name time duration]
|
|
452
|
+
(merge-baked! clip-name {:time time :currentTime time :duration duration})
|
|
453
|
+
(emit-event! event-listeners
|
|
454
|
+
{:type "BAKED_ANIMATION_PROGRESS"
|
|
455
|
+
:clipName clip-name
|
|
456
|
+
:time time
|
|
457
|
+
:duration duration})
|
|
458
|
+
(emit-baked-state! clip-name))
|
|
459
|
+
:emitBakedAnimationParamsChanged emit-baked-param-change!
|
|
460
|
+
:getBakedClips (fn [] (clj->js @baked-clips))
|
|
461
|
+
:getPlayingBakedAnimations (fn [] (clj->js (playing-baked)))
|
|
462
|
+
:getBakedAnimationState (fn [clip-name] (when-let [state (get-baked-state clip-name)] (clj->js state)))
|
|
463
|
+
:updateBakedAnimationState (fn [clip-name state]
|
|
464
|
+
(merge-baked! clip-name (or (value->clj state) {}))
|
|
465
|
+
(emit-baked-state! clip-name)
|
|
466
|
+
(emit-playing-baked!))}})))
|
|
467
|
+
|
|
468
|
+
(defonce animation-event-system (create-event-system))
|
|
469
|
+
|
|
470
|
+
(def animationEventEmitter (:emitter animation-event-system))
|
|
471
|
+
(def snippetList$ (:snippet-list-port animation-event-system))
|
|
472
|
+
(def globalPlaybackState$ (:global-state-port animation-event-system))
|
|
473
|
+
(def bakedClipList$ (:baked-clip-port animation-event-system))
|
|
474
|
+
(def playingBakedAnimations$ (:playing-baked-port animation-event-system))
|
|
475
|
+
(def bakedAnimationProgress$ (:events animation-event-system))
|
|
476
|
+
|
|
477
|
+
(defn bakedAnimationState$ [clip-name]
|
|
478
|
+
((:baked-animation-state-port animation-event-system) clip-name))
|
|
479
|
+
|
|
480
|
+
(defn snippet-state-port [event-emitter snippet-name]
|
|
481
|
+
#js {:subscribe
|
|
482
|
+
(fn [listener]
|
|
483
|
+
(let [subscription (js-call (aget event-emitter "events")
|
|
484
|
+
"subscribe"
|
|
485
|
+
(fn [event]
|
|
486
|
+
(let [event-map (value->clj event)
|
|
487
|
+
type (:type event-map)]
|
|
488
|
+
(when (or (#{"SNIPPET_ADDED" "SNIPPET_REMOVED"} type)
|
|
489
|
+
(= (:snippetName event-map) snippet-name))
|
|
490
|
+
(listener (js-call event-emitter "getSnippet" snippet-name))))))]
|
|
491
|
+
subscription))})
|
|
492
|
+
|
|
493
|
+
(defn snippet-time-port [event-emitter snippet-name]
|
|
494
|
+
#js {:subscribe
|
|
495
|
+
(fn [listener]
|
|
496
|
+
(js-call (aget event-emitter "events")
|
|
497
|
+
"subscribe"
|
|
498
|
+
(fn [event]
|
|
499
|
+
(let [event-map (value->clj event)]
|
|
500
|
+
(when (and (= (:type event-map) "SNIPPET_SEEKED")
|
|
501
|
+
(= (:snippetName event-map) snippet-name))
|
|
502
|
+
(listener (:time event-map)))))))})
|
|
503
|
+
|
|
504
|
+
(defn snippetState$ [snippet-name]
|
|
505
|
+
(snippet-state-port animationEventEmitter snippet-name))
|
|
506
|
+
|
|
507
|
+
(defn snippetTime$ [snippet-name]
|
|
508
|
+
(snippet-time-port animationEventEmitter snippet-name))
|
|
509
|
+
|
|
510
|
+
(defn upsert-snippet [snippets snippet playing?]
|
|
511
|
+
(let [next-snippet (assoc snippet :isPlaying (boolean playing?))]
|
|
512
|
+
(conj (vec (remove #(= (:name %) (:name next-snippet)) snippets)) next-snippet)))
|
|
513
|
+
|
|
514
|
+
(defn patch-snippet [snippets name patch]
|
|
515
|
+
(mapv (fn [snippet]
|
|
516
|
+
(if (= (:name snippet) name)
|
|
517
|
+
(merge snippet patch)
|
|
518
|
+
snippet))
|
|
519
|
+
snippets))
|
|
520
|
+
|
|
521
|
+
(defn find-snippet [snippets name]
|
|
522
|
+
(first (filter #(= (:name %) name) snippets)))
|
|
523
|
+
|
|
524
|
+
(defn agency-schedule! [agency snippet options]
|
|
525
|
+
(js-call agency "scheduleSnippet" (clj->js snippet) (clj->js options)))
|
|
526
|
+
|
|
527
|
+
(defn agency-remove! [agency name]
|
|
528
|
+
(js-call agency "removeSnippet" name))
|
|
529
|
+
|
|
530
|
+
(defn agency-update! [agency name params]
|
|
531
|
+
(js-call agency "updateSnippet" name (clj->js params)))
|
|
532
|
+
|
|
533
|
+
(defn agency-seek! [agency name time-sec]
|
|
534
|
+
(js-call agency "seekSnippet" name time-sec))
|
|
535
|
+
|
|
536
|
+
(defn playback-options [snippet options]
|
|
537
|
+
(merge
|
|
538
|
+
{:autoPlay true
|
|
539
|
+
:sourceAgency (or (:sourceAgency options) "animationService")
|
|
540
|
+
:priority (:snippetPriority snippet)}
|
|
541
|
+
(or options {})))
|
|
542
|
+
|
|
543
|
+
(defn event-system-set-baked-state! [event-system clip-name state]
|
|
544
|
+
((:set-baked-state! event-system) clip-name state))
|
|
545
|
+
|
|
546
|
+
(defn event-system-get-baked-state [event-system clip-name]
|
|
547
|
+
((:get-baked-state event-system) clip-name))
|
|
548
|
+
|
|
549
|
+
(defn event-system-merge-baked-state! [event-system clip-name patch]
|
|
550
|
+
(event-system-set-baked-state!
|
|
551
|
+
event-system
|
|
552
|
+
clip-name
|
|
553
|
+
(merge (or (event-system-get-baked-state event-system clip-name) {}) patch)))
|
|
554
|
+
|
|
555
|
+
(defn call-method [target method-name & args]
|
|
556
|
+
(apply js-call target method-name args))
|
|
557
|
+
|
|
558
|
+
(defn baked-play-options [state]
|
|
559
|
+
(clj->js {:loop (:loop state)
|
|
560
|
+
:loopMode (:loopMode state)
|
|
561
|
+
:repeatCount (:repeatCount state)
|
|
562
|
+
:reverse (:reverse state)
|
|
563
|
+
:playbackRate (:playbackRate state)
|
|
564
|
+
:speed (:playbackRate state)
|
|
565
|
+
:weight (:intensityScale state)
|
|
566
|
+
:intensity (:intensityScale state)
|
|
567
|
+
:blendMode (:requestedBlendMode state)
|
|
568
|
+
:balance (:balance state)
|
|
569
|
+
:easing (:easing state)}))
|
|
570
|
+
|
|
571
|
+
(defn createAnimationService [engine]
|
|
572
|
+
(let [event-system (create-event-system)
|
|
573
|
+
event-emitter (:emitter event-system)
|
|
574
|
+
animation (animation-agency/create-animation-agency #js {:engine engine})
|
|
575
|
+
snippets (atom [])
|
|
576
|
+
baked-engine (atom engine)
|
|
577
|
+
disposed? (atom false)
|
|
578
|
+
playing? (atom false)
|
|
579
|
+
transition-listeners (atom #{})]
|
|
580
|
+
;; Compatibility state belongs to this service instance. A character must
|
|
581
|
+
;; never replace another character's snippet accessor or publish into its
|
|
582
|
+
;; event ports merely because both services came from the same JS module.
|
|
583
|
+
(js-call event-emitter "setSnippetAccessor" (fn [] @snippets))
|
|
584
|
+
(letfn [(notify-transition! []
|
|
585
|
+
(let [snapshot #js {:context #js {:animations (clj->js (mapv clone-snippet @snippets))}}]
|
|
586
|
+
(doseq [listener @transition-listeners]
|
|
587
|
+
(listener snapshot))))
|
|
588
|
+
(load-snippet! [data playing-state options]
|
|
589
|
+
(let [snippet (merge (normalize-snippet data)
|
|
590
|
+
(select-keys options [:snippetPriority]))
|
|
591
|
+
name (:name snippet)]
|
|
592
|
+
(swap! snippets upsert-snippet snippet playing-state)
|
|
593
|
+
(js-call event-emitter "emitSnippetAdded" name)
|
|
594
|
+
(when playing-state
|
|
595
|
+
(agency-schedule! animation snippet (playback-options snippet options))
|
|
596
|
+
(js-call event-emitter "emitPlayStateChanged" name true))
|
|
597
|
+
(notify-transition!)
|
|
598
|
+
name))
|
|
599
|
+
(set-param! [name patch event-params restart?]
|
|
600
|
+
(when-let [snippet (find-snippet @snippets name)]
|
|
601
|
+
(let [next-snippet (merge snippet patch)]
|
|
602
|
+
(swap! snippets patch-snippet name patch)
|
|
603
|
+
(js-call event-emitter "emitParamsChanged" name (clj->js event-params))
|
|
604
|
+
(agency-update! animation name event-params)
|
|
605
|
+
(when (and restart? (:isPlaying next-snippet))
|
|
606
|
+
(agency-remove! animation name)
|
|
607
|
+
(agency-schedule! animation next-snippet (playback-options next-snippet {})))
|
|
608
|
+
(notify-transition!))))
|
|
609
|
+
(set-playing! [name next-playing]
|
|
610
|
+
(when-let [snippet (find-snippet @snippets name)]
|
|
611
|
+
(swap! snippets patch-snippet name {:isPlaying (boolean next-playing)})
|
|
612
|
+
(if next-playing
|
|
613
|
+
(agency-schedule! animation (assoc snippet :isPlaying true) (playback-options snippet {}))
|
|
614
|
+
(agency-remove! animation name))
|
|
615
|
+
(js-call event-emitter "emitPlayStateChanged" name (boolean next-playing))
|
|
616
|
+
(notify-transition!)
|
|
617
|
+
true))
|
|
618
|
+
(play-all! []
|
|
619
|
+
(reset! playing? true)
|
|
620
|
+
(js-call event-emitter "emitGlobalPlaybackChanged" "playing")
|
|
621
|
+
(doseq [snippet @snippets]
|
|
622
|
+
(when (:isPlaying snippet)
|
|
623
|
+
(agency-schedule! animation snippet (playback-options snippet {}))))
|
|
624
|
+
(notify-transition!))
|
|
625
|
+
(pause-all! []
|
|
626
|
+
(reset! playing? false)
|
|
627
|
+
(doseq [snippet @snippets]
|
|
628
|
+
(when (:isPlaying snippet)
|
|
629
|
+
(agency-remove! animation (:name snippet))))
|
|
630
|
+
(js-call event-emitter "emitGlobalPlaybackChanged" "paused")
|
|
631
|
+
(notify-transition!))
|
|
632
|
+
(stop-all! []
|
|
633
|
+
(reset! playing? false)
|
|
634
|
+
(doseq [snippet @snippets]
|
|
635
|
+
(agency-remove! animation (:name snippet)))
|
|
636
|
+
(swap! snippets (fn [entries] (mapv #(assoc % :isPlaying false :currentTime 0) entries)))
|
|
637
|
+
(js-call event-emitter "emitGlobalPlaybackChanged" "stopped")
|
|
638
|
+
(notify-transition!))
|
|
639
|
+
(set-baked-engine! [next-engine]
|
|
640
|
+
(reset! baked-engine next-engine)
|
|
641
|
+
(let [clips (mapv normalize-baked-clip (or (call-method next-engine "getAnimationClips") []))]
|
|
642
|
+
(js-call event-emitter "emitBakedClipsLoaded" (clj->js clips))))
|
|
643
|
+
(current-baked-state [clip-name]
|
|
644
|
+
(or (event-system-get-baked-state event-system clip-name)
|
|
645
|
+
(baked-ui-state {:name clip-name :duration 0 :channels []} nil)))
|
|
646
|
+
(play-baked! [clip-name options]
|
|
647
|
+
(let [base (current-baked-state clip-name)
|
|
648
|
+
option-map (or (value->clj options) {})
|
|
649
|
+
next-state (merge base
|
|
650
|
+
{:isPlaying true
|
|
651
|
+
:isPaused false}
|
|
652
|
+
(select-keys option-map
|
|
653
|
+
[:loop :loopMode :repeatCount :reverse
|
|
654
|
+
:blendMode :requestedBlendMode :balance :easing])
|
|
655
|
+
(when (or (:playbackRate option-map) (:speed option-map))
|
|
656
|
+
{:playbackRate (number-or (:playbackRate option-map) (:speed option-map))
|
|
657
|
+
:speed (number-or (:playbackRate option-map) (:speed option-map))})
|
|
658
|
+
(when (or (:weight option-map) (:intensity option-map))
|
|
659
|
+
{:intensityScale (number-or (:weight option-map) (:intensity option-map))
|
|
660
|
+
:weight (number-or (:weight option-map) (:intensity option-map))}))
|
|
661
|
+
handle (call-method @baked-engine "playAnimation" clip-name (baked-play-options next-state))]
|
|
662
|
+
(js-call event-emitter "emitBakedAnimationStarted" clip-name (clj->js next-state))
|
|
663
|
+
handle))]
|
|
664
|
+
(set-baked-engine! engine)
|
|
665
|
+
(let [api #js {:loadFromJSON
|
|
666
|
+
(fn [data] (load-snippet! data false {}))
|
|
667
|
+
:updateSnippet
|
|
668
|
+
(fn [data]
|
|
669
|
+
(let [snippet (normalize-snippet data)
|
|
670
|
+
name (:name snippet)
|
|
671
|
+
existing (find-snippet @snippets name)
|
|
672
|
+
next-playing (boolean (:isPlaying (or existing snippet)))]
|
|
673
|
+
(swap! snippets upsert-snippet snippet next-playing)
|
|
674
|
+
(js-call event-emitter "emitSnippetUpdated" name)
|
|
675
|
+
(when next-playing
|
|
676
|
+
(agency-remove! animation name)
|
|
677
|
+
(agency-schedule! animation (assoc snippet :isPlaying true) (playback-options snippet {})))
|
|
678
|
+
(notify-transition!)
|
|
679
|
+
name))
|
|
680
|
+
:schedule
|
|
681
|
+
(fn
|
|
682
|
+
([data] (load-snippet! data true {}))
|
|
683
|
+
([data opts]
|
|
684
|
+
(let [options (or (value->clj opts) {})
|
|
685
|
+
priority (number-or (:priority options) (:snippetPriority (or (value->clj data) {})))
|
|
686
|
+
data-with-priority (merge (or (value->clj data) {}) {:snippetPriority priority})]
|
|
687
|
+
(load-snippet! data-with-priority true options))))
|
|
688
|
+
:remove
|
|
689
|
+
(fn [name]
|
|
690
|
+
(agency-remove! animation name)
|
|
691
|
+
(swap! snippets #(vec (remove (fn [snippet] (= (:name snippet) name)) %)))
|
|
692
|
+
(js-call event-emitter "emitSnippetRemoved" name)
|
|
693
|
+
(notify-transition!))
|
|
694
|
+
:play play-all!
|
|
695
|
+
:pause pause-all!
|
|
696
|
+
:stop stop-all!
|
|
697
|
+
:enable (fn [name on?] (set-playing! name (not (false? on?))))
|
|
698
|
+
:seek (fn [name offset-sec]
|
|
699
|
+
(let [time (max 0 (number-or offset-sec 0))]
|
|
700
|
+
(swap! snippets patch-snippet name {:currentTime time})
|
|
701
|
+
(agency-seek! animation name time)
|
|
702
|
+
(js-call event-emitter "emitSnippetSeeked" name time)
|
|
703
|
+
(notify-transition!)
|
|
704
|
+
true))
|
|
705
|
+
:getState (fn [] #js {:context #js {:animations (clj->js (mapv clone-snippet @snippets))}})
|
|
706
|
+
:getScheduleSnapshot
|
|
707
|
+
(fn []
|
|
708
|
+
(clj->js
|
|
709
|
+
(mapv (fn [snippet]
|
|
710
|
+
{:name (:name snippet)
|
|
711
|
+
:enabled (:isPlaying snippet)
|
|
712
|
+
:startsAt 0
|
|
713
|
+
:offset 0
|
|
714
|
+
:localTime (:currentTime snippet)
|
|
715
|
+
:duration (:duration snippet)
|
|
716
|
+
:loop (:loop snippet)
|
|
717
|
+
:priority (:snippetPriority snippet)
|
|
718
|
+
:playbackRate (:snippetPlaybackRate snippet)
|
|
719
|
+
:intensityScale (:snippetIntensityScale snippet)})
|
|
720
|
+
@snippets)))
|
|
721
|
+
:getCurrentValue (fn [au-id] (number-or (call-method engine "getAU" au-id) 0))
|
|
722
|
+
:isPlaying (fn [] @playing?)
|
|
723
|
+
:loadFromLocal
|
|
724
|
+
(fn
|
|
725
|
+
([key] nil)
|
|
726
|
+
([key cat] nil)
|
|
727
|
+
([key cat prio] nil))
|
|
728
|
+
:setSnippetPlaybackRate (fn [name rate]
|
|
729
|
+
(let [next-rate (max 0.0001 (number-or rate 1))]
|
|
730
|
+
(set-param! name
|
|
731
|
+
{:snippetPlaybackRate next-rate}
|
|
732
|
+
{:rate next-rate :playbackRate next-rate}
|
|
733
|
+
false)))
|
|
734
|
+
:setSnippetIntensityScale (fn [name scale]
|
|
735
|
+
(let [next-scale (max 0 (number-or scale 1))]
|
|
736
|
+
(set-param! name
|
|
737
|
+
{:snippetIntensityScale next-scale}
|
|
738
|
+
{:weight next-scale :intensityScale next-scale}
|
|
739
|
+
false)))
|
|
740
|
+
:setSnippetBlendMode (fn [name mode]
|
|
741
|
+
(let [next-mode (if (= mode "additive") "additive" "replace")]
|
|
742
|
+
(set-param! name
|
|
743
|
+
{:snippetBlendMode next-mode}
|
|
744
|
+
{:blendMode next-mode}
|
|
745
|
+
false)))
|
|
746
|
+
:setSnippetBalance (fn [name balance]
|
|
747
|
+
(let [next-balance (max -1 (min 1 (number-or balance 0)))]
|
|
748
|
+
(set-param! name
|
|
749
|
+
{:snippetBalance next-balance}
|
|
750
|
+
{:balance next-balance}
|
|
751
|
+
true)))
|
|
752
|
+
:setSnippetEasing (fn [name easing]
|
|
753
|
+
(set-param! name {:snippetEasing easing} {:easing easing} false))
|
|
754
|
+
:setSnippetPriority (fn [name priority]
|
|
755
|
+
(swap! snippets patch-snippet name {:snippetPriority (number-or priority 0)})
|
|
756
|
+
(notify-transition!))
|
|
757
|
+
:setSnippetLoopMode (fn [name mode]
|
|
758
|
+
(let [next-mode (if (#{"repeat" "once" "pingpong"} mode) mode "once")]
|
|
759
|
+
(set-param! name
|
|
760
|
+
{:mixerLoopMode next-mode :loop (not= next-mode "once")}
|
|
761
|
+
{:loopMode next-mode :mixerLoopMode next-mode :loop (not= next-mode "once")}
|
|
762
|
+
false)))
|
|
763
|
+
:setSnippetRepeatCount (fn [name repeat-count]
|
|
764
|
+
(let [next-repeat (when (and (finite-number? repeat-count) (>= repeat-count 0))
|
|
765
|
+
(js/Math.floor repeat-count))]
|
|
766
|
+
(set-param! name
|
|
767
|
+
{:mixerRepeatCount next-repeat}
|
|
768
|
+
{:repeatCount next-repeat}
|
|
769
|
+
false)))
|
|
770
|
+
:setSnippetReverse (fn [name reverse?]
|
|
771
|
+
(set-param! name
|
|
772
|
+
{:mixerReverse (boolean reverse?)}
|
|
773
|
+
{:reverse (boolean reverse?)}
|
|
774
|
+
false))
|
|
775
|
+
:setSnippetPlaying set-playing!
|
|
776
|
+
:setSnippetTime (fn [name time-sec]
|
|
777
|
+
(let [time (max 0 (number-or time-sec 0))]
|
|
778
|
+
(swap! snippets patch-snippet name {:currentTime time})
|
|
779
|
+
(agency-seek! animation name time)
|
|
780
|
+
(js-call event-emitter "emitSnippetSeeked" name time)
|
|
781
|
+
(notify-transition!)))
|
|
782
|
+
:setSnippetLoopState (fn [name iteration local-time]
|
|
783
|
+
(swap! snippets patch-snippet name
|
|
784
|
+
(cond-> {:loopIteration (max 0 (number-or iteration 0))}
|
|
785
|
+
(finite-number? local-time) (assoc :lastLoopTime local-time)))
|
|
786
|
+
(notify-transition!))
|
|
787
|
+
:pauseSnippet (fn [name] (set-playing! name false))
|
|
788
|
+
:resumeSnippet (fn [name] (set-playing! name true))
|
|
789
|
+
:restartSnippet (fn [name]
|
|
790
|
+
(when-let [snippet (find-snippet @snippets name)]
|
|
791
|
+
(agency-remove! animation name)
|
|
792
|
+
(agency-schedule! animation (assoc snippet :isPlaying true :currentTime 0)
|
|
793
|
+
(playback-options snippet {}))
|
|
794
|
+
(swap! snippets patch-snippet name {:isPlaying true :currentTime 0})
|
|
795
|
+
(js-call event-emitter "emitPlayStateChanged" name true)
|
|
796
|
+
true))
|
|
797
|
+
:stopSnippet (fn [name] (set-playing! name false))
|
|
798
|
+
:onTransition (fn [listener]
|
|
799
|
+
(swap! transition-listeners conj listener)
|
|
800
|
+
(fn [] (swap! transition-listeners disj listener)))
|
|
801
|
+
:dispose (fn []
|
|
802
|
+
(when-not @disposed?
|
|
803
|
+
(reset! disposed? true)
|
|
804
|
+
(js-call animation "dispose")
|
|
805
|
+
(reset! snippets [])
|
|
806
|
+
(js-call event-emitter "setSnippetAccessor" (fn [] []))
|
|
807
|
+
(js-call event-emitter "emitBakedClipsLoaded" #js [])))
|
|
808
|
+
:debug (fn [] (.log js/console "[Polymer AnimationService]" (clj->js @snippets)))
|
|
809
|
+
:eventEmitter event-emitter
|
|
810
|
+
:events (aget event-emitter "events")
|
|
811
|
+
:snippetList$ (:snippet-list-port event-system)
|
|
812
|
+
:globalPlaybackState$ (:global-state-port event-system)
|
|
813
|
+
:bakedClipList$ (:baked-clip-port event-system)
|
|
814
|
+
:playingBakedAnimations$ (:playing-baked-port event-system)
|
|
815
|
+
:bakedAnimationProgress$ (:events event-system)
|
|
816
|
+
:snippetState$ (fn [snippet-name] (snippet-state-port event-emitter snippet-name))
|
|
817
|
+
:snippetTime$ (fn [snippet-name] (snippet-time-port event-emitter snippet-name))
|
|
818
|
+
:bakedAnimationState$ (:baked-animation-state-port event-system)
|
|
819
|
+
:setBakedAnimationEngine set-baked-engine!
|
|
820
|
+
:playBakedAnimation
|
|
821
|
+
(fn
|
|
822
|
+
([clip-name] (play-baked! clip-name nil))
|
|
823
|
+
([clip-name options] (play-baked! clip-name options)))
|
|
824
|
+
:stopBakedAnimation (fn [clip-name]
|
|
825
|
+
(call-method @baked-engine "stopAnimation" clip-name)
|
|
826
|
+
(js-call event-emitter "emitBakedAnimationStopped" clip-name))
|
|
827
|
+
:pauseBakedAnimation (fn [clip-name]
|
|
828
|
+
(call-method @baked-engine "pauseAnimation" clip-name)
|
|
829
|
+
(js-call event-emitter "emitBakedAnimationPaused" clip-name))
|
|
830
|
+
:resumeBakedAnimation (fn [clip-name]
|
|
831
|
+
(call-method @baked-engine "resumeAnimation" clip-name)
|
|
832
|
+
(js-call event-emitter "emitBakedAnimationResumed" clip-name))
|
|
833
|
+
:setBakedAnimationSpeed (fn [clip-name speed]
|
|
834
|
+
(let [next-speed (max 0.1 (number-or speed 1))]
|
|
835
|
+
(call-method @baked-engine "setAnimationSpeed" clip-name next-speed)
|
|
836
|
+
(event-system-merge-baked-state! event-system clip-name {:speed next-speed :playbackRate next-speed})
|
|
837
|
+
(js-call event-emitter "emitBakedAnimationParamsChanged" clip-name
|
|
838
|
+
(clj->js {:speed next-speed
|
|
839
|
+
:playbackRate next-speed}))))
|
|
840
|
+
:setBakedAnimationWeight (fn [clip-name weight]
|
|
841
|
+
(let [next-weight (max 0 (number-or weight 1))]
|
|
842
|
+
(call-method @baked-engine "setAnimationIntensity" clip-name next-weight)
|
|
843
|
+
(event-system-merge-baked-state! event-system clip-name {:weight next-weight :intensityScale next-weight})
|
|
844
|
+
(js-call event-emitter "emitBakedAnimationParamsChanged" clip-name
|
|
845
|
+
(clj->js {:weight next-weight
|
|
846
|
+
:intensityScale next-weight}))))
|
|
847
|
+
:setBakedAnimationLoop (fn [clip-name loop?]
|
|
848
|
+
(let [mode (if loop? "repeat" "once")]
|
|
849
|
+
(call-method @baked-engine "setAnimationLoopMode" clip-name mode)
|
|
850
|
+
(event-system-merge-baked-state! event-system clip-name {:loop (boolean loop?) :loopMode mode})
|
|
851
|
+
(js-call event-emitter "emitBakedAnimationParamsChanged" clip-name
|
|
852
|
+
(clj->js {:loop (boolean loop?)
|
|
853
|
+
:loopMode mode}))))
|
|
854
|
+
:setBakedAnimationLoopMode (fn [clip-name mode]
|
|
855
|
+
(let [next-mode (if (#{"repeat" "once" "pingpong"} mode) mode "once")]
|
|
856
|
+
(call-method @baked-engine "setAnimationLoopMode" clip-name next-mode)
|
|
857
|
+
(event-system-merge-baked-state! event-system clip-name {:loop (not= next-mode "once") :loopMode next-mode})
|
|
858
|
+
(js-call event-emitter "emitBakedAnimationParamsChanged" clip-name
|
|
859
|
+
(clj->js {:loop (not= next-mode "once")
|
|
860
|
+
:loopMode next-mode}))))
|
|
861
|
+
:setBakedAnimationRepeatCount (fn [clip-name repeat-count]
|
|
862
|
+
(let [next-repeat (when (and (finite-number? repeat-count) (>= repeat-count 0))
|
|
863
|
+
(js/Math.floor repeat-count))]
|
|
864
|
+
(call-method @baked-engine "setAnimationRepeatCount" clip-name next-repeat)
|
|
865
|
+
(event-system-merge-baked-state! event-system clip-name {:repeatCount next-repeat})
|
|
866
|
+
(js-call event-emitter "emitBakedAnimationParamsChanged" clip-name
|
|
867
|
+
(clj->js {:repeatCount next-repeat}))))
|
|
868
|
+
:setBakedAnimationReverse (fn [clip-name reverse?]
|
|
869
|
+
(call-method @baked-engine "setAnimationReverse" clip-name (boolean reverse?))
|
|
870
|
+
(event-system-merge-baked-state! event-system clip-name {:reverse (boolean reverse?)})
|
|
871
|
+
(js-call event-emitter "emitBakedAnimationParamsChanged" clip-name
|
|
872
|
+
(clj->js {:reverse (boolean reverse?)})))
|
|
873
|
+
:setBakedAnimationBlendMode (fn [clip-name mode]
|
|
874
|
+
(let [next-mode (if (= mode "additive") "additive" "replace")]
|
|
875
|
+
(call-method @baked-engine "setAnimationBlendMode" clip-name next-mode)
|
|
876
|
+
(event-system-merge-baked-state! event-system clip-name {:blendMode next-mode :requestedBlendMode next-mode})
|
|
877
|
+
(js-call event-emitter "emitBakedAnimationParamsChanged" clip-name
|
|
878
|
+
(clj->js {:blendMode next-mode}))))
|
|
879
|
+
:setBakedAnimationBalance (fn [clip-name balance]
|
|
880
|
+
(let [next-balance (max -1 (min 1 (number-or balance 0)))]
|
|
881
|
+
(event-system-merge-baked-state! event-system clip-name {:balance next-balance})
|
|
882
|
+
(js-call event-emitter "emitBakedAnimationParamsChanged" clip-name
|
|
883
|
+
(clj->js {:balance next-balance}))))
|
|
884
|
+
:setBakedAnimationEasing (fn [clip-name easing]
|
|
885
|
+
(event-system-merge-baked-state! event-system clip-name {:easing easing})
|
|
886
|
+
(js-call event-emitter "emitBakedAnimationParamsChanged" clip-name
|
|
887
|
+
(clj->js {:easing easing})))
|
|
888
|
+
:seekBakedAnimation (fn [clip-name time-sec]
|
|
889
|
+
(let [time (max 0 (number-or time-sec 0))
|
|
890
|
+
duration (:duration (current-baked-state clip-name))]
|
|
891
|
+
(or (call-method @baked-engine "seekAnimation" clip-name time)
|
|
892
|
+
(call-method @baked-engine "setAnimationTime" clip-name time)
|
|
893
|
+
(call-method @baked-engine "setTime" clip-name time))
|
|
894
|
+
(event-system-merge-baked-state! event-system clip-name {:time time :currentTime time})
|
|
895
|
+
(js-call event-emitter "emitBakedAnimationProgress" clip-name time duration)
|
|
896
|
+
true))
|
|
897
|
+
:canSeekBakedAnimation (fn []
|
|
898
|
+
(boolean (or (aget @baked-engine "seekAnimation")
|
|
899
|
+
(aget @baked-engine "setAnimationTime")
|
|
900
|
+
(aget @baked-engine "setTime"))))
|
|
901
|
+
:stopAllBakedAnimations (fn []
|
|
902
|
+
(let [active (js->clj (js-call event-emitter "getPlayingBakedAnimations")
|
|
903
|
+
:keywordize-keys true)]
|
|
904
|
+
(call-method @baked-engine "stopAllAnimations")
|
|
905
|
+
(doseq [animation active]
|
|
906
|
+
(js-call event-emitter "emitBakedAnimationStopped" (:name animation)))))
|
|
907
|
+
:getBakedClips (fn [] (js-call event-emitter "getBakedClips"))
|
|
908
|
+
:getPlayingBakedAnimations (fn [] (js-call event-emitter "getPlayingBakedAnimations"))}]
|
|
909
|
+
(js/Object.defineProperty api "playing" #js {:get (fn [] @playing?)})
|
|
910
|
+
api))))
|
|
911
|
+
|
|
912
|
+
(defn get-storage [storage]
|
|
913
|
+
(or storage
|
|
914
|
+
(try
|
|
915
|
+
(aget js/globalThis "localStorage")
|
|
916
|
+
(catch :default _ nil))))
|
|
917
|
+
|
|
918
|
+
(defn parse-json [raw fallback]
|
|
919
|
+
(if (and raw (string? raw))
|
|
920
|
+
(try
|
|
921
|
+
(js->clj (.parse js/JSON raw) :keywordize-keys true)
|
|
922
|
+
(catch :default _ fallback))
|
|
923
|
+
fallback))
|
|
924
|
+
|
|
925
|
+
(defn parse-string-list [raw]
|
|
926
|
+
(let [parsed (parse-json raw [])]
|
|
927
|
+
(if (sequential? parsed)
|
|
928
|
+
(vec (filter string? parsed))
|
|
929
|
+
[])))
|
|
930
|
+
|
|
931
|
+
(defn getBundledSnippetNames [list-key]
|
|
932
|
+
;; Polymer no longer ships Latticework's TS snippet glob catalog. User-authored
|
|
933
|
+
;; snippets remain available through localStorage; built-ins can be ported as
|
|
934
|
+
;; Polymer data in a separate focused PR.
|
|
935
|
+
#js [])
|
|
936
|
+
|
|
937
|
+
(defn getStoredSnippetNames
|
|
938
|
+
([list-key] (getStoredSnippetNames list-key nil))
|
|
939
|
+
([list-key storage]
|
|
940
|
+
(if-not (snippet-categories list-key)
|
|
941
|
+
#js []
|
|
942
|
+
(let [resolved-storage (get-storage storage)]
|
|
943
|
+
(if-not resolved-storage
|
|
944
|
+
#js []
|
|
945
|
+
(clj->js (parse-string-list (.getItem resolved-storage list-key))))))))
|
|
946
|
+
|
|
947
|
+
(defn getAvailableSnippetNames
|
|
948
|
+
([list-key] (getAvailableSnippetNames list-key nil))
|
|
949
|
+
([list-key storage]
|
|
950
|
+
(getStoredSnippetNames list-key storage)))
|
|
951
|
+
|
|
952
|
+
(defn resolveSnippetEntry
|
|
953
|
+
([list-key name] (resolveSnippetEntry list-key name nil))
|
|
954
|
+
([list-key name storage]
|
|
955
|
+
(js/Promise.
|
|
956
|
+
(fn [resolve _reject]
|
|
957
|
+
(let [resolved-storage (get-storage storage)
|
|
958
|
+
storage-key (str list-key "/" name)
|
|
959
|
+
raw (when resolved-storage (.getItem resolved-storage storage-key))
|
|
960
|
+
parsed (parse-json raw nil)]
|
|
961
|
+
(resolve
|
|
962
|
+
(when (map? parsed)
|
|
963
|
+
(clj->js {:name (string-or (:name parsed) name)
|
|
964
|
+
:data parsed
|
|
965
|
+
:source "localStorage"
|
|
966
|
+
:storageKey storage-key}))))))))
|
|
967
|
+
|
|
968
|
+
(defn preloadAllSnippets [] nil)
|
|
969
|
+
|
|
970
|
+
(defn clearPreloadedSnippets
|
|
971
|
+
([] (clearPreloadedSnippets nil))
|
|
972
|
+
([storage]
|
|
973
|
+
(let [resolved-storage (get-storage storage)
|
|
974
|
+
manifest (when resolved-storage
|
|
975
|
+
(parse-json (.getItem resolved-storage bundled-snippet-manifest-key) {}))]
|
|
976
|
+
(when resolved-storage
|
|
977
|
+
(doseq [[list-key names] manifest
|
|
978
|
+
name names]
|
|
979
|
+
(.removeItem resolved-storage (str (key->string list-key) "/" name)))
|
|
980
|
+
(.removeItem resolved-storage bundled-snippet-manifest-key)
|
|
981
|
+
(.removeItem resolved-storage bundled-snippet-version-key)))))
|