@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.
Files changed (85) hide show
  1. package/README.md +226 -0
  2. package/dist/index.d.ts +2603 -0
  3. package/dist/index.js +1031 -0
  4. package/dist/test.cjs +1519 -0
  5. package/dist/worker/agency-worker.js +972 -0
  6. package/dist/worker/manifest.edn +1 -0
  7. package/package.json +57 -0
  8. package/shadow-cljs.edn +173 -0
  9. package/src/polymer/animation/agency.cljs +84 -0
  10. package/src/polymer/animation/domain.cljs +110 -0
  11. package/src/polymer/animation/planner.cljs +88 -0
  12. package/src/polymer/animation/runtime.cljs +137 -0
  13. package/src/polymer/animation/scheduler.cljs +178 -0
  14. package/src/polymer/animation/service.cljs +981 -0
  15. package/src/polymer/animation/state.cljs +112 -0
  16. package/src/polymer/blink/agency.cljs +131 -0
  17. package/src/polymer/blink/planner.cljs +115 -0
  18. package/src/polymer/blink/scheduler.cljs +90 -0
  19. package/src/polymer/blink/snippet.cljs +50 -0
  20. package/src/polymer/blink/state.cljs +102 -0
  21. package/src/polymer/camera_context/agency.cljs +132 -0
  22. package/src/polymer/camera_context/domain.cljs +186 -0
  23. package/src/polymer/camera_context/planner.cljs +50 -0
  24. package/src/polymer/camera_context/scheduler.cljs +113 -0
  25. package/src/polymer/camera_context/state.cljs +121 -0
  26. package/src/polymer/character.cljs +471 -0
  27. package/src/polymer/conversation/agency.cljs +83 -0
  28. package/src/polymer/conversation/domain.cljs +35 -0
  29. package/src/polymer/conversation/effector.cljs +130 -0
  30. package/src/polymer/conversation/planner.cljs +178 -0
  31. package/src/polymer/conversation/scheduler.cljs +202 -0
  32. package/src/polymer/conversation/service.cljs +329 -0
  33. package/src/polymer/conversation/state.cljs +186 -0
  34. package/src/polymer/core.cljs +172 -0
  35. package/src/polymer/eye_head/agency.cljs +75 -0
  36. package/src/polymer/eye_head/domain.cljs +133 -0
  37. package/src/polymer/eye_head/goap.cljs +106 -0
  38. package/src/polymer/eye_head/scheduler.cljs +176 -0
  39. package/src/polymer/eye_head/service.cljs +259 -0
  40. package/src/polymer/eye_head/snippet.cljs +92 -0
  41. package/src/polymer/eye_head/state.cljs +167 -0
  42. package/src/polymer/gaze/agency.cljs +85 -0
  43. package/src/polymer/gaze/domain.cljs +211 -0
  44. package/src/polymer/gaze/goap.cljs +208 -0
  45. package/src/polymer/gaze/scheduler.cljs +184 -0
  46. package/src/polymer/gaze/state.cljs +202 -0
  47. package/src/polymer/gesture/agency.cljs +185 -0
  48. package/src/polymer/gesture/goap.cljs +461 -0
  49. package/src/polymer/gesture/scheduler.cljs +122 -0
  50. package/src/polymer/gesture/snippet.cljs +198 -0
  51. package/src/polymer/gesture/state.cljs +273 -0
  52. package/src/polymer/lipsync/agency.cljs +593 -0
  53. package/src/polymer/lipsync/articulation/jaw.cljs +258 -0
  54. package/src/polymer/lipsync/articulation/snippet.cljs +605 -0
  55. package/src/polymer/lipsync/articulation/tongue.cljs +418 -0
  56. package/src/polymer/lipsync/articulation/visemes.cljs +429 -0
  57. package/src/polymer/lipsync/goap.cljs +132 -0
  58. package/src/polymer/lipsync/pronunciation/effector.cljs +51 -0
  59. package/src/polymer/lipsync/scheduler.cljs +302 -0
  60. package/src/polymer/lipsync/state.cljs +202 -0
  61. package/src/polymer/nlp/phonemes.cljs +164 -0
  62. package/src/polymer/prosodic/agency.cljs +196 -0
  63. package/src/polymer/prosodic/goap.cljs +122 -0
  64. package/src/polymer/prosodic/scheduler.cljs +99 -0
  65. package/src/polymer/prosodic/snippet.cljs +106 -0
  66. package/src/polymer/prosodic/state.cljs +139 -0
  67. package/src/polymer/runtime/embody.cljs +117 -0
  68. package/src/polymer/runtime/embody_wasm.cljs +177 -0
  69. package/src/polymer/runtime/rust_embody_host.cljs +645 -0
  70. package/src/polymer/society.cljs +239 -0
  71. package/src/polymer/stream.cljs +130 -0
  72. package/src/polymer/transcription/agency.cljs +69 -0
  73. package/src/polymer/transcription/domain.cljs +68 -0
  74. package/src/polymer/transcription/effector.cljs +611 -0
  75. package/src/polymer/transcription/planner.cljs +203 -0
  76. package/src/polymer/transcription/scheduler.cljs +225 -0
  77. package/src/polymer/transcription/service.cljs +209 -0
  78. package/src/polymer/transcription/state.cljs +239 -0
  79. package/src/polymer/tts/agency.cljs +503 -0
  80. package/src/polymer/tts/azure.cljs +290 -0
  81. package/src/polymer/tts/planner.cljs +178 -0
  82. package/src/polymer/tts/runtime.cljs +624 -0
  83. package/src/polymer/tts/scheduler.cljs +162 -0
  84. package/src/polymer/tts/state.cljs +191 -0
  85. package/src/polymer/worker.cljs +70 -0
@@ -0,0 +1,259 @@
1
+ (ns polymer.eye-head.service
2
+ (:require [polymer.animation.service :as animation-service]
3
+ [polymer.eye-head.agency :as eye-head-agency]))
4
+
5
+ ;; Compatibility adapter for the JavaScript service API LoomLarge already uses.
6
+ ;; This is not the Eye/Head agency implementation. Movement work enters
7
+ ;; Polymer's Eye/Head agency first; this adapter only preserves the historical
8
+ ;; API while routing accepted animation requests into Polymer Animation.
9
+
10
+ (def EYE_AUS
11
+ #js {:BOTH_LOOK_LEFT 61
12
+ :BOTH_LOOK_RIGHT 62
13
+ :BOTH_LOOK_UP 63
14
+ :BOTH_LOOK_DOWN 64
15
+ :BLINK 43
16
+ :WIDE 5
17
+ :SQUINT 7})
18
+
19
+ (def HEAD_AUS
20
+ #js {:TURN_LEFT 51
21
+ :TURN_RIGHT 52
22
+ :TURN_UP 53
23
+ :TURN_DOWN 54
24
+ :TILT_LEFT 55
25
+ :TILT_RIGHT 56})
26
+
27
+ (def DEFAULT_EYE_HEAD_CONFIG
28
+ #js {:gazeMode "engine"
29
+ :eyeTrackingEnabled false
30
+ :headTrackingEnabled false
31
+ :headFollowEyes true
32
+ :eyeIntensity 1.0
33
+ :headIntensity 0.5
34
+ :returnToNeutralEnabled false
35
+ :returnToNeutralDelay 3000
36
+ :returnToNeutralDuration 800
37
+ :useAnimationAgency true})
38
+
39
+ (def DEFAULT_ANIMATION_KEYS
40
+ #js {:EYE_LOOK_LEFT "eyeHeadTracking/eyeLookLeft"
41
+ :EYE_LOOK_RIGHT "eyeHeadTracking/eyeLookRight"
42
+ :EYE_LOOK_UP "eyeHeadTracking/eyeLookUp"
43
+ :EYE_LOOK_DOWN "eyeHeadTracking/eyeLookDown"
44
+ :EYE_BLINK "eyeHeadTracking/eyeBlink"
45
+ :HEAD_TURN_LEFT "eyeHeadTracking/headTurnLeft"
46
+ :HEAD_TURN_RIGHT "eyeHeadTracking/headTurnRight"
47
+ :HEAD_TURN_UP "eyeHeadTracking/headTurnUp"
48
+ :HEAD_TURN_DOWN "eyeHeadTracking/headTurnDown"
49
+ :HEAD_TILT_LEFT "eyeHeadTracking/headTiltLeft"
50
+ :HEAD_TILT_RIGHT "eyeHeadTracking/headTiltRight"})
51
+
52
+ (defn data-map [value]
53
+ (cond
54
+ (map? value) value
55
+ value (js->clj value :keywordize-keys true)
56
+ :else {}))
57
+
58
+ (defn finite-number? [value]
59
+ (and (number? value) (js/isFinite value)))
60
+
61
+ (defn number-or [value fallback]
62
+ (if (finite-number? value) value fallback))
63
+
64
+ (defn js-call [target method-name & args]
65
+ (when target
66
+ (when-let [method (aget target method-name)]
67
+ (when (fn? method)
68
+ (.apply method target (to-array args))))))
69
+
70
+ (defn runtime-prop [value property-name]
71
+ (cond
72
+ (map? value) (get value (keyword property-name))
73
+ value (aget value property-name)
74
+ :else nil))
75
+
76
+ (defn normalize-target [target fallback]
77
+ (let [input (merge {:x 0 :y 0 :z 0} fallback (data-map target))]
78
+ {:x (max -1 (min 1 (number-or (:x input) 0)))
79
+ :y (max -1 (min 1 (number-or (:y input) 0)))
80
+ :z (number-or (:z input) 0)}))
81
+
82
+ (defn service-config [config]
83
+ (let [input (data-map config)
84
+ transition (number-or (:agencyTransitionDuration input)
85
+ (number-or (:transitionDurationMs input) 240))
86
+ neutral-duration (number-or (:returnToNeutralDuration input)
87
+ (number-or (:returnToCenterDurationMs input) 300))]
88
+ {:enabled true
89
+ :eyeTrackingEnabled (boolean (:eyeTrackingEnabled input))
90
+ :headTrackingEnabled (boolean (:headTrackingEnabled input))
91
+ :headFollowEyes (if (contains? input :headFollowEyes)
92
+ (boolean (:headFollowEyes input))
93
+ true)
94
+ :eyeIntensity (number-or (:eyeIntensity input) 1.0)
95
+ :headIntensity (number-or (:headIntensity input) 0.5)
96
+ :transitionDurationMs transition
97
+ :returnToCenterDurationMs neutral-duration
98
+ :coalesceMs (number-or (:coalesceMs input) 0)
99
+ :replaceExisting true}))
100
+
101
+ (defn service-state [state-atom mode-atom]
102
+ (let [snapshot @state-atom
103
+ agency-state (data-map snapshot)
104
+ config (or (:config agency-state) {})
105
+ current-target (or (:currentTarget agency-state) {:x 0 :y 0 :z 0})]
106
+ #js {:eyeStatus (if (get config :eyeTrackingEnabled) "tracking" "idle")
107
+ :headStatus (if (get config :headTrackingEnabled) "tracking" "idle")
108
+ :currentGaze (clj->js current-target)
109
+ :targetGaze (clj->js current-target)
110
+ :eyeIntensity (number-or (:eyeIntensity config) 1.0)
111
+ :lastBlinkTime (:lastBlinkTime agency-state)
112
+ :headIntensity (number-or (:headIntensity config) 0.5)
113
+ :headFollowTimer nil
114
+ :isSpeaking (boolean (:isSpeaking agency-state))
115
+ :isListening (boolean (:isListening agency-state))
116
+ :returnToNeutralTimer nil
117
+ :lastGazeUpdateTime (or (:lastGazeUpdateTime agency-state) 0)
118
+ :mode @mode-atom}))
119
+
120
+ (defn create-blink-snippet []
121
+ (let [name (str "eyeHeadTracking:blink:" (.now js/Date))]
122
+ {:name name
123
+ :curves {"43" [{:time 0.0 :intensity 0.0 :inherit true}
124
+ {:time 0.045 :intensity 1.0}
125
+ {:time 0.13 :intensity 0.0}]}
126
+ :channels [{:target {:type "au" :id 43}
127
+ :keyframes [{:time 0.0 :intensity 0.0 :inherit true}
128
+ {:time 0.045 :intensity 1.0}
129
+ {:time 0.13 :intensity 0.0}]}]
130
+ :maxTime 0.13
131
+ :loop false
132
+ :snippetCategory "eyeHeadTracking"
133
+ :snippetPriority 25
134
+ :snippetBlendMode "additive"
135
+ :snippetPlaybackRate 1.0
136
+ :snippetIntensityScale 1.0}))
137
+
138
+ (defn animation-target [config]
139
+ (or (runtime-prop config "animationAgency")
140
+ (when-let [engine (runtime-prop config "engine")]
141
+ (animation-service/createAnimationService engine))))
142
+
143
+ (defn schedule-animation! [anim snippet options]
144
+ (or (js-call anim "schedule" (clj->js snippet) (clj->js options))
145
+ (js-call anim "scheduleSnippet" (clj->js snippet) (clj->js options))))
146
+
147
+ (defn remove-animation! [anim name]
148
+ (or (js-call anim "remove" name)
149
+ (js-call anim "removeSnippet" name)))
150
+
151
+ (defn createEyeHeadTrackingService
152
+ ([]
153
+ (createEyeHeadTrackingService nil nil))
154
+ ([config]
155
+ (createEyeHeadTrackingService config nil))
156
+ ([config callbacks]
157
+ (let [config-atom (atom (service-config config))
158
+ mode-atom (atom (or (:gazeMode (data-map config)) "manual"))
159
+ state-atom (atom (merge {:isSpeaking false
160
+ :isListening false
161
+ :lastBlinkTime nil
162
+ :lastGazeUpdateTime 0}
163
+ {:config @config-atom
164
+ :currentTarget {:x 0 :y 0 :z 0}}))
165
+ agency (eye-head-agency/create-eye-head-tracking-agency (clj->js @config-atom))
166
+ anim (animation-target config)
167
+ started? (atom false)
168
+ disposed? (atom false)
169
+ unsubscribers (atom [])]
170
+ (letfn [(dispatch! [command]
171
+ (when-not @disposed?
172
+ (js-call agency "dispatch" (clj->js command))))
173
+ (configure! [next-config]
174
+ (swap! config-atom merge (service-config next-config))
175
+ (swap! state-atom assoc :config @config-atom)
176
+ (dispatch! {:type "configure" :config @config-atom}))
177
+ (route-event! [event]
178
+ (let [payload (data-map event)]
179
+ (case (:type payload)
180
+ "animation.requestScheduleSnippet"
181
+ (schedule-animation! anim (:snippet payload) (or (:options payload) {:autoPlay true}))
182
+
183
+ "animation.requestRemoveSnippet"
184
+ (remove-animation! anim (:name payload))
185
+
186
+ "error"
187
+ (js-call callbacks "onError" (js/Error. (or (:message payload)
188
+ "Eye/head tracking error")))
189
+
190
+ nil)
191
+ (when (= "animation.requestScheduleSnippet" (:type payload))
192
+ (swap! state-atom assoc
193
+ :currentTarget (get-in payload [:snippet :metadata :target] (:currentTarget @state-atom))
194
+ :lastGazeUpdateTime (.now js/Date)))))
195
+ (teardown! []
196
+ (doseq [unsubscribe @unsubscribers]
197
+ (unsubscribe))
198
+ (reset! unsubscribers []))]
199
+ (swap! unsubscribers conj (js-call agency "subscribeEvents" route-event!))
200
+ #js {:start (fn []
201
+ (when-not @disposed?
202
+ (reset! started? true)
203
+ (dispatch! {:type "enable"})))
204
+ :stop (fn []
205
+ (when-not @disposed?
206
+ (reset! started? false)
207
+ (dispatch! {:type "cancel" :reason "stopped"})))
208
+ :setGazeTarget (fn [target]
209
+ (let [normalized (normalize-target target nil)]
210
+ (swap! state-atom assoc
211
+ :currentTarget normalized
212
+ :lastGazeUpdateTime (.now js/Date))
213
+ (dispatch! {:type "setTarget"
214
+ :target normalized
215
+ :mode @mode-atom})))
216
+ :resetToNeutral (fn
217
+ ([] (dispatch! {:type "reset"}))
218
+ ([duration-ms]
219
+ (dispatch! {:type "reset" :durationMs duration-ms})))
220
+ :blink (fn []
221
+ (when-not @disposed?
222
+ (let [snippet (create-blink-snippet)]
223
+ (swap! state-atom assoc :lastBlinkTime (.now js/Date))
224
+ (schedule-animation! anim snippet {:autoPlay true
225
+ :sourceAgency "eyeHeadTracking"}))))
226
+ :setSpeaking (fn [is-speaking]
227
+ (swap! state-atom assoc :isSpeaking (boolean is-speaking))
228
+ (if is-speaking
229
+ (js-call callbacks "onHeadStart")
230
+ (js-call callbacks "onHeadStop")))
231
+ :setListening (fn [is-listening]
232
+ (swap! state-atom assoc :isListening (boolean is-listening))
233
+ (if is-listening
234
+ (js-call callbacks "onEyeStart")
235
+ (js-call callbacks "onEyeStop")))
236
+ :setEyeBlendWeight (fn [_value] nil)
237
+ :setHeadBlendWeight (fn [_value] nil)
238
+ :updateConfig configure!
239
+ :getState (fn [] (service-state state-atom mode-atom))
240
+ :getMachineContext (fn []
241
+ #js {:config (clj->js @config-atom)
242
+ :mode @mode-atom
243
+ :target (clj->js (:currentTarget @state-atom))
244
+ :lastApplied (clj->js (:currentTarget @state-atom))
245
+ :status #js {:eye (if (:eyeTrackingEnabled @config-atom) "tracking" "idle")
246
+ :head (if (:headTrackingEnabled @config-atom) "tracking" "idle")}})
247
+ :getSnippets (fn [] #js {:eye (js/Map.) :head (js/Map.)})
248
+ :setMode (fn [mode]
249
+ (reset! mode-atom mode)
250
+ (dispatch! {:type "configure" :config {:mode mode}}))
251
+ :getMode (fn [] @mode-atom)
252
+ :subscribeToWebcam (fn [_callback] (fn [] nil))
253
+ :getWebcamVideoElement (fn [] nil)
254
+ :isWebcamActive (fn [] false)
255
+ :dispose (fn []
256
+ (when-not @disposed?
257
+ (reset! disposed? true)
258
+ (teardown!)
259
+ (js-call agency "dispose")))}))))
@@ -0,0 +1,92 @@
1
+ (ns polymer.eye-head.snippet)
2
+
3
+ ;; Eye/Head snippets use typed AU channels. Numeric curve keys remain for
4
+ ;; compatibility with older animation builds, while typed channels are the
5
+ ;; canonical contract for the current Animation agency.
6
+
7
+ (def eye-yaw-left 61)
8
+ (def eye-yaw-right 62)
9
+ (def eye-pitch-up 63)
10
+ (def eye-pitch-down 64)
11
+ (def head-yaw-left 51)
12
+ (def head-yaw-right 52)
13
+ (def head-pitch-up 53)
14
+ (def head-pitch-down 54)
15
+ (def head-roll-left 55)
16
+ (def head-roll-right 56)
17
+
18
+ (defn seconds
19
+ [duration-ms]
20
+ (/ (max 1 duration-ms) 1000))
21
+
22
+ (defn axis-curves
23
+ [negative-au positive-au value duration-sec]
24
+ (let [value (max -1 (min 1 value))
25
+ negative-value (if (neg? value) (js/Math.abs value) 0)
26
+ positive-value (if (pos? value) value 0)]
27
+ {(str negative-au) [{:time 0 :intensity 0 :inherit true}
28
+ {:time duration-sec :intensity negative-value}]
29
+ (str positive-au) [{:time 0 :intensity 0 :inherit true}
30
+ {:time duration-sec :intensity positive-value}]}))
31
+
32
+ (defn au-channel
33
+ [au-id curve]
34
+ {:target {:type "au" :id au-id}
35
+ :keyframes curve})
36
+
37
+ (defn curves->channels
38
+ [curves]
39
+ (into []
40
+ (map (fn [[au-id curve]]
41
+ (au-channel (js/parseInt au-id 10) curve)))
42
+ curves))
43
+
44
+ (defn merge-curves
45
+ [& curve-maps]
46
+ (apply merge curve-maps))
47
+
48
+ (defn build-eye-curves
49
+ [request]
50
+ (let [target (:target request)
51
+ duration-sec (seconds (:eyeDurationMs request))
52
+ yaw (* (:x target) (:eyeIntensity request))
53
+ pitch (* (:y target) (:eyeIntensity request))]
54
+ (merge-curves
55
+ (axis-curves eye-yaw-left eye-yaw-right yaw duration-sec)
56
+ (axis-curves eye-pitch-down eye-pitch-up pitch duration-sec))))
57
+
58
+ (defn build-head-curves
59
+ [request]
60
+ (let [target (:target request)
61
+ duration-sec (seconds (:headDurationMs request))
62
+ yaw (* (:x target) (:headIntensity request))
63
+ pitch (* (:y target) (:headIntensity request))
64
+ roll (* (:headRoll request) (:headIntensity request))]
65
+ (merge-curves
66
+ (axis-curves head-yaw-left head-yaw-right yaw duration-sec)
67
+ (axis-curves head-pitch-down head-pitch-up pitch duration-sec)
68
+ (axis-curves head-roll-left head-roll-right roll duration-sec))))
69
+
70
+ (defn build-gaze-snippet
71
+ [request config]
72
+ (let [eye-curves (when (:eyeEnabled request) (build-eye-curves request))
73
+ head-curves (when (and (:headEnabled request) (:headFollowEyes request))
74
+ (build-head-curves request))
75
+ curves (merge-curves (or eye-curves {}) (or head-curves {}))
76
+ channels (curves->channels curves)
77
+ max-time (max (seconds (:eyeDurationMs request))
78
+ (seconds (:headDurationMs request)))]
79
+ {:name (str "eyeHeadTracking:gaze:" (:requestId request))
80
+ :curves curves
81
+ :channels channels
82
+ :maxTime max-time
83
+ :loop false
84
+ :mixerClampWhenFinished true
85
+ :snippetPriority (:snippetPriority config)
86
+ :snippetPlaybackRate 1.0
87
+ :snippetIntensityScale 1.0
88
+ :metadata {:agency "eyeHeadTracking"
89
+ :sourceAgency (:sourceAgency request)
90
+ :requestId (:requestId request)
91
+ :mode (:mode request)
92
+ :target (:target request)}}))
@@ -0,0 +1,167 @@
1
+ (ns polymer.eye-head.state
2
+ (:require [polymer.eye-head.domain :as domain]))
3
+
4
+ ;; Eye/Head state tracks the local movement schedule. Runtime handles remain
5
+ ;; private to the Animation agency; this agency only records plain request and
6
+ ;; snippet metadata.
7
+
8
+ (def default-config
9
+ {:enabled true
10
+ :eyeTrackingEnabled true
11
+ :headTrackingEnabled true
12
+ :headFollowEyes true
13
+ :eyeIntensity 1.0
14
+ :headIntensity 0.5
15
+ :headRoll 0
16
+ :eyePriority 20
17
+ :headPriority 15
18
+ :snippetPriority 20
19
+ :transitionDurationMs 240
20
+ :returnToCenterDurationMs 300
21
+ :coalesceMs 0
22
+ :replaceExisting true})
23
+
24
+ (defn input-value
25
+ [input camel-key kebab-key fallback]
26
+ (let [camel (get input camel-key ::missing)
27
+ kebab (get input kebab-key ::missing)]
28
+ (cond
29
+ (not= camel ::missing) camel
30
+ (not= kebab ::missing) kebab
31
+ :else fallback)))
32
+
33
+ (defn normalize-config
34
+ [config]
35
+ (let [input (merge default-config (domain/data-map config))]
36
+ {:enabled (boolean (input-value input :enabled :enabled (:enabled default-config)))
37
+ :eyeTrackingEnabled (boolean (input-value input :eyeTrackingEnabled :eye-tracking-enabled (:eyeTrackingEnabled default-config)))
38
+ :headTrackingEnabled (boolean (input-value input :headTrackingEnabled :head-tracking-enabled (:headTrackingEnabled default-config)))
39
+ :headFollowEyes (boolean (input-value input :headFollowEyes :head-follow-eyes (:headFollowEyes default-config)))
40
+ :eyeIntensity (domain/clamp 0 2
41
+ (domain/number-or (input-value input :eyeIntensity :eye-intensity nil)
42
+ (:eyeIntensity default-config)))
43
+ :headIntensity (domain/clamp 0 2
44
+ (domain/number-or (input-value input :headIntensity :head-intensity nil)
45
+ (:headIntensity default-config)))
46
+ :headRoll (domain/clamp -1 1
47
+ (domain/number-or (input-value input :headRoll :head-roll nil)
48
+ (:headRoll default-config)))
49
+ :eyePriority (int (domain/clamp 0 100
50
+ (domain/number-or (input-value input :eyePriority :eye-priority nil)
51
+ (:eyePriority default-config))))
52
+ :headPriority (int (domain/clamp 0 100
53
+ (domain/number-or (input-value input :headPriority :head-priority nil)
54
+ (:headPriority default-config))))
55
+ :snippetPriority (int (domain/clamp 0 100
56
+ (domain/number-or (input-value input :snippetPriority :snippet-priority nil)
57
+ (:snippetPriority default-config))))
58
+ :transitionDurationMs (int (domain/clamp 50 3000
59
+ (domain/number-or
60
+ (input-value input :transitionDurationMs :transition-duration-ms nil)
61
+ (:transitionDurationMs default-config))))
62
+ :returnToCenterDurationMs (int (domain/clamp 50 3000
63
+ (domain/number-or
64
+ (input-value input :returnToCenterDurationMs :return-to-center-duration-ms nil)
65
+ (:returnToCenterDurationMs default-config))))
66
+ :coalesceMs (int (domain/clamp 0 1000
67
+ (domain/number-or (input-value input :coalesceMs :coalesce-ms nil)
68
+ (:coalesceMs default-config))))
69
+ :replaceExisting (boolean (input-value input :replaceExisting :replace-existing (:replaceExisting default-config)))}))
70
+
71
+ (defn default-state
72
+ [config]
73
+ {:agency "eyeHeadTracking"
74
+ :status "idle"
75
+ :mode "manual"
76
+ :currentTarget domain/zero-target
77
+ :pendingRequest nil
78
+ :lastRequest nil
79
+ :lastSnippet nil
80
+ :activeSnippetNames []
81
+ :lastIgnored nil
82
+ :lastPlan nil
83
+ :scheduledCount 0
84
+ :removedCount 0
85
+ :resetCount 0
86
+ :cancelCount 0
87
+ :ignoredCount 0
88
+ :config (normalize-config config)})
89
+
90
+ (defn config->state
91
+ [config]
92
+ (default-state config))
93
+
94
+ (defn visible-state
95
+ [state]
96
+ (clj->js state))
97
+
98
+ (defn configure
99
+ [state config]
100
+ (assoc state :config (normalize-config (merge (:config state)
101
+ (domain/data-map config)))))
102
+
103
+ (defn set-enabled
104
+ [state enabled]
105
+ (assoc-in state [:config :enabled] (boolean enabled)))
106
+
107
+ (defn record-plan
108
+ [state plan]
109
+ (assoc state :lastPlan plan))
110
+
111
+ (defn record-pending
112
+ [state request]
113
+ (assoc state
114
+ :status "pending"
115
+ :pendingRequest request))
116
+
117
+ (defn record-scheduled
118
+ [state request snippet published-at]
119
+ (-> state
120
+ (assoc :status "tracking"
121
+ :mode (:mode request)
122
+ :currentTarget (:target request)
123
+ :pendingRequest nil
124
+ :lastRequest request
125
+ :lastSnippet snippet
126
+ :activeSnippetNames [(:name snippet)]
127
+ :lastPlan nil
128
+ :lastEvent {:type "animation.requestScheduleSnippet"
129
+ :name (:name snippet)
130
+ :requestId (:requestId request)
131
+ :at published-at})
132
+ (update :scheduledCount inc)))
133
+
134
+ (defn record-removed
135
+ [state name reason removed-at]
136
+ (-> state
137
+ (assoc :activeSnippetNames []
138
+ :lastEvent {:type "animation.requestRemoveSnippet"
139
+ :name name
140
+ :reason reason
141
+ :at removed-at})
142
+ (update :removedCount inc)))
143
+
144
+ (defn record-ignored
145
+ [state ignored]
146
+ (-> state
147
+ (assoc :status "ignored"
148
+ :lastIgnored ignored
149
+ :pendingRequest nil
150
+ :lastEvent ignored)
151
+ (update :ignoredCount inc)))
152
+
153
+ (defn record-reset
154
+ [state request snippet published-at]
155
+ (-> (record-scheduled state request snippet published-at)
156
+ (assoc :status "reset"
157
+ :currentTarget domain/zero-target)
158
+ (update :resetCount inc)))
159
+
160
+ (defn record-cancel
161
+ [state request]
162
+ (-> state
163
+ (assoc :status "cancelled"
164
+ :pendingRequest nil
165
+ :activeSnippetNames []
166
+ :lastEvent request)
167
+ (update :cancelCount inc)))
@@ -0,0 +1,85 @@
1
+ (ns polymer.gaze.agency
2
+ (:require [polymer.gaze.domain :as domain]
3
+ [polymer.gaze.goap :as goap]
4
+ [polymer.gaze.scheduler :as scheduler]
5
+ [polymer.gaze.state :as state]
6
+ [polymer.stream :as stream]))
7
+
8
+ ;; Gaze converts attention and camera-relative facts into look-intent requests.
9
+ ;; It does not move eyes or head directly. The downstream movement agency owns
10
+ ;; the runtime side effects after accepting these request messages.
11
+
12
+ (defn now-ms
13
+ []
14
+ (.now js/Date))
15
+
16
+ (defn create-gaze-agency
17
+ [config]
18
+ (let [input-stream (stream/create-stream)
19
+ event-stream (stream/create-stream)
20
+ state-atom (atom (state/config->state config))
21
+ disposed? (atom false)
22
+ emit-input (:emit input-stream)
23
+ emit-event (:emit event-stream)
24
+ scheduler-atom (atom nil)]
25
+ (letfn [(dispatch! [command]
26
+ (when-not @disposed?
27
+ (let [payload (domain/data-map command)
28
+ plan (goap/plan-command payload @state-atom (now-ms))]
29
+ (emit-input {:type "command"
30
+ :agency "gaze"
31
+ :command payload})
32
+ (swap! state-atom assoc :lastPlan plan)
33
+ (when-let [agency-scheduler @scheduler-atom]
34
+ ((:schedule agency-scheduler) plan)))))
35
+
36
+ (dispatch-type! [type]
37
+ (dispatch! #js {:type type}))]
38
+ (let [agency-scheduler (scheduler/create-scheduler
39
+ {:state-atom state-atom
40
+ :emit-event emit-event})]
41
+ (reset! scheduler-atom agency-scheduler)
42
+ #js {:dispatch dispatch!
43
+ :snapshot (fn [] (state/visible-state @state-atom))
44
+ :input (stream/writable-port input-stream dispatch!)
45
+ :events (stream/readable-port event-stream)
46
+ :subscribeInput (fn [listener] ((:subscribe input-stream) listener))
47
+ :subscribeEvents (fn [listener] ((:subscribe event-stream) listener))
48
+ :subscribe (fn [listener] ((:subscribe event-stream) listener))
49
+ :subscribeStatus (fn [listener] ((:subscribe event-stream) listener))
50
+ :setTarget (fn
51
+ ([target] (dispatch! #js {:type "setTarget"
52
+ :target target}))
53
+ ([target options] (dispatch! #js {:type "setTarget"
54
+ :target target
55
+ :options options})))
56
+ :focusTarget (fn [target] (dispatch! #js {:type "focusTarget"
57
+ :target target}))
58
+ :configure (fn [config] (dispatch! #js {:type "configure"
59
+ :config config}))
60
+ :setMode (fn [mode] (dispatch! #js {:type "setMode"
61
+ :mode mode}))
62
+ :setActive (fn [active] (dispatch! #js {:type "setActive"
63
+ :active active}))
64
+ :enable (fn [] (dispatch-type! "enable"))
65
+ :disable (fn [] (dispatch-type! "disable"))
66
+ :reset (fn
67
+ ([] (dispatch-type! "reset"))
68
+ ([duration-ms] (dispatch! #js {:type "reset"
69
+ :durationMs duration-ms})))
70
+ :cancel (fn
71
+ ([] (dispatch-type! "cancel"))
72
+ ([reason] (dispatch! #js {:type "cancel"
73
+ :reason reason})))
74
+ :flush (fn []
75
+ (when-let [agency-scheduler @scheduler-atom]
76
+ ((:flush agency-scheduler))))
77
+ :queue (fn []
78
+ (when-let [agency-scheduler @scheduler-atom]
79
+ (clj->js ((:queue agency-scheduler)))))
80
+ :dispose (fn []
81
+ (when-not @disposed?
82
+ (reset! disposed? true)
83
+ ((:dispose agency-scheduler))
84
+ ((:dispose input-stream))
85
+ ((:dispose event-stream))))}))))