brilliantsole 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 (158) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +21 -0
  3. package/build/brilliantsole.cjs +5957 -0
  4. package/build/brilliantsole.cjs.map +1 -0
  5. package/build/brilliantsole.js +5448 -0
  6. package/build/brilliantsole.js.map +1 -0
  7. package/build/brilliantsole.ls.js +4872 -0
  8. package/build/brilliantsole.ls.js.map +1 -0
  9. package/build/brilliantsole.min.js +6 -0
  10. package/build/brilliantsole.min.js.map +1 -0
  11. package/build/brilliantsole.module.d.ts +908 -0
  12. package/build/brilliantsole.module.js +5412 -0
  13. package/build/brilliantsole.module.js.map +1 -0
  14. package/build/brilliantsole.module.min.d.ts +908 -0
  15. package/build/brilliantsole.module.min.js +6 -0
  16. package/build/brilliantsole.module.min.js.map +1 -0
  17. package/build/brilliantsole.node.module.d.ts +908 -0
  18. package/build/brilliantsole.node.module.js +5906 -0
  19. package/build/brilliantsole.node.module.js.map +1 -0
  20. package/build/dts/BS.d.ts +25 -0
  21. package/build/dts/Device.d.ts +136 -0
  22. package/build/dts/DeviceInformationManager.d.ts +56 -0
  23. package/build/dts/DeviceManager.d.ts +67 -0
  24. package/build/dts/FileTransferManager.d.ts +84 -0
  25. package/build/dts/FirmwareManager.d.ts +71 -0
  26. package/build/dts/InformationManager.d.ts +66 -0
  27. package/build/dts/TfliteManager.d.ts +92 -0
  28. package/build/dts/connection/BaseConnectionManager.d.ts +59 -0
  29. package/build/dts/connection/ClientConnectionManager.d.ts +23 -0
  30. package/build/dts/connection/WebSocketClientConnectionManager.d.ts +23 -0
  31. package/build/dts/connection/bluetooth/BluetoothConnectionManager.d.ts +10 -0
  32. package/build/dts/connection/bluetooth/NobleConnectionManager.d.ts +42 -0
  33. package/build/dts/connection/bluetooth/WebBluetoothConnectionManager.d.ts +20 -0
  34. package/build/dts/connection/bluetooth/bluetoothUUIDs.d.ts +14 -0
  35. package/build/dts/connection/webSocket/ClientConnectionManager.d.ts +23 -0
  36. package/build/dts/connection/webSocket/WebSocketClientConnectionManager.d.ts +23 -0
  37. package/build/dts/devicePair/DevicePair.d.ts +60 -0
  38. package/build/dts/devicePair/DevicePairPressureSensorDataManager.d.ts +25 -0
  39. package/build/dts/devicePair/DevicePairSensorDataManager.d.ts +33 -0
  40. package/build/dts/scanner/BaseScanner.d.ts +66 -0
  41. package/build/dts/scanner/NobleScanner.d.ts +17 -0
  42. package/build/dts/scanner/Scanner.d.ts +3 -0
  43. package/build/dts/sensor/BarometerSensorDataManager.d.ts +16 -0
  44. package/build/dts/sensor/MotionSensorDataManager.d.ts +69 -0
  45. package/build/dts/sensor/PressureSensorDataManager.d.ts +36 -0
  46. package/build/dts/sensor/SensorConfigurationManager.d.ts +44 -0
  47. package/build/dts/sensor/SensorDataManager.d.ts +40 -0
  48. package/build/dts/server/BaseClient.d.ts +85 -0
  49. package/build/dts/server/BaseServer.d.ts +48 -0
  50. package/build/dts/server/ServerUtils.d.ts +23 -0
  51. package/build/dts/server/udp/UDPServer.d.ts +11 -0
  52. package/build/dts/server/udp/UDPUtils.d.ts +9 -0
  53. package/build/dts/server/websocket/WebSocketClient.d.ts +17 -0
  54. package/build/dts/server/websocket/WebSocketServer.d.ts +13 -0
  55. package/build/dts/server/websocket/WebSocketUtils.d.ts +9 -0
  56. package/build/dts/utils/ArrayBufferUtils.d.ts +7 -0
  57. package/build/dts/utils/ArrayUtils.d.ts +2 -0
  58. package/build/dts/utils/CenterOfPressureHelper.d.ts +15 -0
  59. package/build/dts/utils/Console.d.ts +34 -0
  60. package/build/dts/utils/EventDispatcher.d.ts +50 -0
  61. package/build/dts/utils/EventUtils.d.ts +6 -0
  62. package/build/dts/utils/MathUtils.d.ts +21 -0
  63. package/build/dts/utils/ParseUtils.d.ts +5 -0
  64. package/build/dts/utils/RangeHelper.d.ts +8 -0
  65. package/build/dts/utils/Text.d.ts +6 -0
  66. package/build/dts/utils/Timer.d.ts +14 -0
  67. package/build/dts/utils/TypeScriptUtils.d.ts +19 -0
  68. package/build/dts/utils/cbor.d.ts +6 -0
  69. package/build/dts/utils/checksum.d.ts +3 -0
  70. package/build/dts/utils/environment.d.ts +13 -0
  71. package/build/dts/utils/mcumgr.d.ts +88 -0
  72. package/build/dts/utils/stringUtils.d.ts +2 -0
  73. package/build/dts/vibration/VibrationManager.d.ts +45 -0
  74. package/build/dts/vibration/VibrationWaveformEffects.d.ts +2 -0
  75. package/build/index.d.ts +908 -0
  76. package/build/index.node.d.ts +908 -0
  77. package/examples/3d/index.html +109 -0
  78. package/examples/3d/scene.html +57 -0
  79. package/examples/3d/script.js +419 -0
  80. package/examples/balance/index.html +138 -0
  81. package/examples/balance/script.js +243 -0
  82. package/examples/basic/index.html +327 -0
  83. package/examples/basic/script.js +1093 -0
  84. package/examples/center-of-pressure/index.html +132 -0
  85. package/examples/center-of-pressure/script.js +207 -0
  86. package/examples/device-pair/index.html +72 -0
  87. package/examples/device-pair/script.js +187 -0
  88. package/examples/edge-impulse/index.html +94 -0
  89. package/examples/edge-impulse/script.js +1033 -0
  90. package/examples/graph/index.html +83 -0
  91. package/examples/graph/script.js +469 -0
  92. package/examples/machine-learning/index.html +366 -0
  93. package/examples/machine-learning/script.js +1774 -0
  94. package/examples/pressure/index.html +145 -0
  95. package/examples/pressure/script.js +201 -0
  96. package/examples/recording/index.html +187 -0
  97. package/examples/recording/script.js +736 -0
  98. package/examples/server/index.html +266 -0
  99. package/examples/server/script.js +925 -0
  100. package/examples/utils/aframe/fingertip-button-component.js +201 -0
  101. package/examples/utils/aframe/fingertip-collider-target-component.js +102 -0
  102. package/examples/utils/aframe/fingertip-colliders-component.js +147 -0
  103. package/examples/utils/three/three.module.min.js +24846 -0
  104. package/examples/webxr/index.html +221 -0
  105. package/examples/webxr/script.js +1127 -0
  106. package/package.json +83 -0
  107. package/src/BS.ts +68 -0
  108. package/src/Device.ts +734 -0
  109. package/src/DeviceInformationManager.ts +146 -0
  110. package/src/DeviceManager.ts +354 -0
  111. package/src/FileTransferManager.ts +452 -0
  112. package/src/FirmwareManager.ts +357 -0
  113. package/src/InformationManager.ts +283 -0
  114. package/src/TfliteManager.ts +450 -0
  115. package/src/connection/BaseConnectionManager.ts +255 -0
  116. package/src/connection/ClientConnectionManager.ts +120 -0
  117. package/src/connection/bluetooth/BluetoothConnectionManager.ts +34 -0
  118. package/src/connection/bluetooth/NobleConnectionManager.ts +302 -0
  119. package/src/connection/bluetooth/WebBluetoothConnectionManager.ts +269 -0
  120. package/src/connection/bluetooth/bluetoothUUIDs.ts +218 -0
  121. package/src/devicePair/DevicePair.ts +253 -0
  122. package/src/devicePair/DevicePairPressureSensorDataManager.ts +82 -0
  123. package/src/devicePair/DevicePairSensorDataManager.ts +90 -0
  124. package/src/scanner/BaseScanner.ts +189 -0
  125. package/src/scanner/NobleScanner.ts +195 -0
  126. package/src/scanner/Scanner.ts +16 -0
  127. package/src/sensor/BarometerSensorDataManager.ts +41 -0
  128. package/src/sensor/MotionSensorDataManager.ts +151 -0
  129. package/src/sensor/PressureSensorDataManager.ts +112 -0
  130. package/src/sensor/SensorConfigurationManager.ts +177 -0
  131. package/src/sensor/SensorDataManager.ts +166 -0
  132. package/src/server/BaseClient.ts +368 -0
  133. package/src/server/BaseServer.ts +344 -0
  134. package/src/server/ServerUtils.ts +93 -0
  135. package/src/server/udp/UDPServer.ts +229 -0
  136. package/src/server/udp/UDPUtils.ts +20 -0
  137. package/src/server/websocket/WebSocketClient.ts +179 -0
  138. package/src/server/websocket/WebSocketServer.ts +184 -0
  139. package/src/server/websocket/WebSocketUtils.ts +20 -0
  140. package/src/utils/ArrayBufferUtils.ts +88 -0
  141. package/src/utils/ArrayUtils.ts +15 -0
  142. package/src/utils/CenterOfPressureHelper.ts +39 -0
  143. package/src/utils/Console.ts +156 -0
  144. package/src/utils/EventDispatcher.ts +153 -0
  145. package/src/utils/EventUtils.ts +41 -0
  146. package/src/utils/MathUtils.ts +53 -0
  147. package/src/utils/ParseUtils.ts +46 -0
  148. package/src/utils/RangeHelper.ts +38 -0
  149. package/src/utils/Text.ts +30 -0
  150. package/src/utils/Timer.ts +72 -0
  151. package/src/utils/TypeScriptUtils.ts +22 -0
  152. package/src/utils/cbor.js +429 -0
  153. package/src/utils/checksum.ts +41 -0
  154. package/src/utils/environment.ts +46 -0
  155. package/src/utils/mcumgr.js +444 -0
  156. package/src/utils/stringUtils.ts +11 -0
  157. package/src/vibration/VibrationManager.ts +308 -0
  158. package/src/vibration/VibrationWaveformEffects.ts +128 -0
@@ -0,0 +1,1127 @@
1
+ import * as BS from "../../build/brilliantsole.module.js";
2
+ window.BS = BS;
3
+ console.log({ BS });
4
+ //BS.setAllConsoleLevelFlags({ log: true });
5
+
6
+ const client = new BS.WebSocketClient();
7
+ console.log({ client });
8
+
9
+ window.client = client;
10
+
11
+ const devicePair = BS.DevicePair.shared;
12
+ window.devicePair = devicePair;
13
+
14
+ // SCENE SETUP
15
+
16
+ /** @type {HTMLElement} */
17
+ const desktopEntity = document.getElementById("desktop");
18
+
19
+ /** @type {Object.<string, HTMLElement>} */
20
+ const handTrackingControllers = {
21
+ left: document.querySelector(".left.hand"),
22
+ right: document.querySelector(".right.hand"),
23
+ };
24
+
25
+ /** @type {HTMLElement} */
26
+ const scene = document.querySelector("a-scene");
27
+
28
+ // HAND TRACKING
29
+
30
+ Object.entries(handTrackingControllers).forEach(([side, handTrackingController]) => {
31
+ handTrackingController.addEventListener("fingertiptouchstarted", (event) => {
32
+ const { fingerName, withEl, withFinger, onSameHand } = event.detail;
33
+ });
34
+ handTrackingController.addEventListener("fingertiptouchended", (event) => {
35
+ const { fingerName, withEl, withFinger, onSameHand } = event.detail;
36
+ });
37
+ });
38
+
39
+ // DOUBLE PINCH
40
+
41
+ class DebouncedFunction {
42
+ /**
43
+ * @param {()=>{}} callback
44
+ * @param {number} interval
45
+ */
46
+ constructor(callback, interval) {
47
+ this.#callback = callback;
48
+ this.#interval = interval;
49
+ }
50
+
51
+ /** @type {number} */
52
+ #interval;
53
+
54
+ /** @type {()=>{}} */
55
+ #callback;
56
+
57
+ /** @type {number?} */
58
+ #timeoutId = null;
59
+
60
+ trigger() {
61
+ this.cancel();
62
+ this.#timeoutId = setTimeout(() => {
63
+ this.#callback();
64
+ this.#timeoutId = null;
65
+ }, this.#interval);
66
+ }
67
+
68
+ cancel() {
69
+ if (this.#timeoutId != null) {
70
+ clearTimeout(this.#timeoutId);
71
+ this.#timeoutId = null;
72
+ }
73
+ }
74
+ }
75
+
76
+ Object.entries(handTrackingControllers).forEach(([side, handTrackingController]) => {
77
+ let numberOfPinches = 0;
78
+ const resetNumberOfPinches = () => {
79
+ numberOfPinches = 0;
80
+ };
81
+ const debouncedResetNumberOfPinches = new DebouncedFunction(resetNumberOfPinches, 1000);
82
+ console.log({ side, handTrackingController });
83
+
84
+ const onPinchStarted = () => {
85
+ //console.log("throttled pinch");
86
+
87
+ numberOfPinches++;
88
+ //console.log({ side, numberOfPinches });
89
+ if (numberOfPinches == 1) {
90
+ debouncedResetNumberOfPinches.trigger();
91
+ } else if (numberOfPinches == 2) {
92
+ handTrackingController.dispatchEvent(new Event("doublepinch"));
93
+ numberOfPinches = 0;
94
+ debouncedResetNumberOfPinches.cancel();
95
+ }
96
+ };
97
+
98
+ const throttledOnPinchStarted = AFRAME.utils.throttle(onPinchStarted, 300);
99
+
100
+ handTrackingController.addEventListener("fingertiptouchstarted", (event) => {
101
+ const { finger, withEl, withFinger, onSameHand } = event.detail;
102
+
103
+ //console.log({ finger, withEl, withFinger, onSameHand });
104
+
105
+ const isPinch = finger == "index" && onSameHand && withFinger == "thumb";
106
+ if (!isPinch) {
107
+ return;
108
+ }
109
+
110
+ throttledOnPinchStarted();
111
+ });
112
+ });
113
+
114
+ // DESKTOP PLACEMENT
115
+
116
+ scene.addEventListener("ar-hit-test-select", (event) => {
117
+ //console.log(event);
118
+ setARHitTest(false);
119
+ });
120
+ function getIsARHitTestEnabled() {
121
+ return scene.getAttribute("ar-hit-test").enabled;
122
+ }
123
+ const toggleARHitTest = () => {
124
+ const isARHitTestEnabled = getIsARHitTestEnabled();
125
+ setARHitTest(!isARHitTestEnabled);
126
+ };
127
+ /** @param {boolean} enabled */
128
+ const setARHitTest = (enabled) => {
129
+ if (getIsARHitTestEnabled() == enabled) {
130
+ return;
131
+ }
132
+ console.log("ar-hit-test", enabled);
133
+ scene.setAttribute("ar-hit-test", "enabled", enabled);
134
+ window.dispatchEvent(new CustomEvent("ar-hit-test", { detail: { enabled } }));
135
+ };
136
+ window.setARHitTest = setARHitTest;
137
+
138
+ handTrackingControllers.right.addEventListener("doublepinch", () => {
139
+ console.log("double pinch");
140
+ //toggleARHitTest();
141
+ });
142
+
143
+ const toggleARHitTestEntity = scene.querySelector(".toggleARHitTest");
144
+ toggleARHitTestEntity.addEventListener("click", () => {
145
+ toggleARHitTest();
146
+ });
147
+ window.addEventListener("ar-hit-test", (event) => {
148
+ const { enabled } = event.detail;
149
+ let text, color;
150
+ if (enabled) {
151
+ text = "cancel";
152
+ color = "red";
153
+ } else {
154
+ text = "move";
155
+ color = "white";
156
+ }
157
+ toggleARHitTestEntity.setAttribute("fingertip-button", {
158
+ text,
159
+ color,
160
+ });
161
+ });
162
+
163
+ // WEBSOCKET URL SEARCH PARAMS
164
+
165
+ const url = new URL(location);
166
+ function setUrlParam(key, value) {
167
+ if (history.pushState) {
168
+ let searchParams = new URLSearchParams(window.location.search);
169
+ if (value) {
170
+ searchParams.set(key, value);
171
+ } else {
172
+ searchParams.delete(key);
173
+ }
174
+ let newUrl =
175
+ window.location.protocol + "//" + window.location.host + window.location.pathname + "?" + searchParams.toString();
176
+ window.history.pushState({ path: newUrl }, "", newUrl);
177
+ }
178
+ }
179
+ client.addEventListener("isConnected", () => {
180
+ if (client.isConnected) {
181
+ setUrlParam("webSocketUrl", client.webSocket.url);
182
+ webSocketUrlInput.value = client.webSocket.url;
183
+ webSocketUrlInput.dispatchEvent(new Event("input"));
184
+ } else {
185
+ setUrlParam("webSocketUrl");
186
+ }
187
+ });
188
+
189
+ // WEBSOCKET SERVER URL
190
+
191
+ /** @type {HTMLInputElement} */
192
+ const webSocketUrlInput = document.getElementById("webSocketUrl");
193
+ webSocketUrlInput.value = url.searchParams.get("webSocketUrl") || "";
194
+
195
+ const toggleSetWebSocketUrlButton = scene.querySelector(".toggleSetWebSocketUrl");
196
+ client.addEventListener("isConnected", () => {
197
+ toggleSetWebSocketUrlButton.setAttribute("fingertip-button", {
198
+ disabled: client.isConnected,
199
+ });
200
+ });
201
+ toggleSetWebSocketUrlButton.addEventListener("click", () => {
202
+ webSocketUrlInput.focus();
203
+ });
204
+ webSocketUrlInput.addEventListener("input", () => {
205
+ toggleSetWebSocketUrlButton.setAttribute("fingertip-button", {
206
+ text: webSocketUrlInput.value || "localhost",
207
+ });
208
+ });
209
+ webSocketUrlInput.addEventListener("focusin", () => {
210
+ toggleSetWebSocketUrlButton.setAttribute("fingertip-button", { color: "yellow" });
211
+ });
212
+ webSocketUrlInput.addEventListener("focusout", () => {
213
+ toggleSetWebSocketUrlButton.setAttribute("fingertip-button", { color: "white" });
214
+ });
215
+ webSocketUrlInput.dispatchEvent(new Event("input"));
216
+
217
+ // WEBSOCKET CONNECTION
218
+
219
+ /** @type {HTMLButtonElement} */
220
+ const toggleConnectionButton = document.getElementById("toggleConnection");
221
+ toggleConnectionButton.addEventListener("click", () => {
222
+ if (client.isConnected) {
223
+ client.disconnect();
224
+ } else {
225
+ /** @type {string?} */
226
+ let webSocketUrl;
227
+ if (webSocketUrlInput.value.length > 0) {
228
+ webSocketUrl = webSocketUrlInput.value;
229
+ }
230
+ client.connect(webSocketUrl);
231
+ }
232
+ });
233
+ client.addEventListener("connectionStatus", () => {
234
+ switch (client.connectionStatus) {
235
+ case "connected":
236
+ case "notConnected":
237
+ toggleConnectionButton.disabled = false;
238
+ toggleConnectionButton.innerText = client.isConnected ? "disconnect" : "connect";
239
+ break;
240
+ case "connecting":
241
+ case "disconnecting":
242
+ toggleConnectionButton.innerText = client.connectionStatus;
243
+ toggleConnectionButton.disabled = true;
244
+ break;
245
+ }
246
+ });
247
+
248
+ const toggleConnectionEntity = scene.querySelector(".toggleConnection");
249
+ client.addEventListener("connectionStatus", (event) => {
250
+ let disabled;
251
+ let text;
252
+
253
+ switch (client.connectionStatus) {
254
+ case "connected":
255
+ case "notConnected":
256
+ text = client.isConnected ? "disconnect" : "connect";
257
+ disabled = false;
258
+ break;
259
+ case "connecting":
260
+ case "disconnecting":
261
+ text = client.connectionStatus;
262
+ disabled = true;
263
+ break;
264
+ }
265
+
266
+ toggleConnectionEntity.setAttribute("fingertip-button", { disabled, text });
267
+ });
268
+ toggleConnectionEntity.addEventListener("click", () => {
269
+ /** @type {string?} */
270
+ let webSocketUrl;
271
+ if (webSocketUrlInput.value.length > 0) {
272
+ webSocketUrl = webSocketUrlInput.value;
273
+ }
274
+ client.toggleConnection(webSocketUrl);
275
+ });
276
+
277
+ // SCANNER
278
+
279
+ /** @type {HTMLInputElement} */
280
+ const isScanningAvailableCheckbox = document.getElementById("isScanningAvailable");
281
+ client.addEventListener("isScanningAvailable", () => {
282
+ isScanningAvailableCheckbox.checked = client.isScanningAvailable;
283
+ });
284
+
285
+ /** @type {HTMLButtonElement} */
286
+ const toggleScanButton = document.getElementById("toggleScan");
287
+ toggleScanButton.addEventListener("click", () => {
288
+ client.toggleScan();
289
+ });
290
+ client.addEventListener("isScanningAvailable", () => {
291
+ toggleScanButton.disabled = !client.isScanningAvailable;
292
+ });
293
+ client.addEventListener("isScanning", () => {
294
+ toggleScanButton.innerText = client.isScanning ? "stop scanning" : "scan";
295
+ });
296
+
297
+ const toggleScanEntity = scene.querySelector(".toggleScan");
298
+ toggleScanEntity.addEventListener("click", () => {
299
+ client.toggleScan();
300
+ });
301
+ client.addEventListener("isScanning", () => {
302
+ toggleScanEntity.setAttribute("fingertip-button", {
303
+ text: client.isScanning ? "stop scan" : "scan",
304
+ });
305
+ });
306
+ client.addEventListener("isConnected", () => {
307
+ toggleScanEntity.setAttribute("fingertip-button", {
308
+ disabled: !client.isConnected,
309
+ });
310
+ });
311
+
312
+ // SCREEN
313
+
314
+ const screenEntity = scene.querySelector(".screen");
315
+ const screenTitle = screenEntity.querySelector(".title");
316
+
317
+ /** @typedef {"none" | "discoveredDevices" | "availableDevices" | "devicePair"} ScreenMode */
318
+ /** @type {ScreenMode} */
319
+ let screenMode;
320
+ /** @param {ScreenMode} newScreenMode */
321
+ function setScreenMode(newScreenMode) {
322
+ if (screenMode == newScreenMode) {
323
+ console.log("redundant screenMode assignment", newScreenMode);
324
+ return;
325
+ }
326
+ screenMode = newScreenMode;
327
+ window.dispatchEvent(new CustomEvent("screenMode", { detail: { screenMode } }));
328
+ }
329
+
330
+ window.addEventListener("screenMode", () => {
331
+ let showScreen = screenMode != "none";
332
+ screenEntity.object3D.visible = showScreen;
333
+ });
334
+ setScreenMode("none");
335
+
336
+ client.addEventListener("isConnected", () => {
337
+ if (!client.isConnected) {
338
+ setScreenMode("none");
339
+ }
340
+ });
341
+
342
+ window.addEventListener("screenMode", () => {
343
+ let text = "";
344
+
345
+ switch (screenMode) {
346
+ case "availableDevices":
347
+ text = "available devices";
348
+ break;
349
+ case "discoveredDevices":
350
+ text = "discovered devices";
351
+ break;
352
+ case "devicePair":
353
+ text = "device pair";
354
+ break;
355
+ }
356
+
357
+ screenTitle.setAttribute("value", text);
358
+ });
359
+
360
+ // DISCOVERED DEVICES
361
+
362
+ const discoveredDevicesEntity = scene.querySelector(".discoveredDevices");
363
+ /** @type {HTMLTemplateElement} */
364
+ const discoveredDeviceEntityTemplate = discoveredDevicesEntity.querySelector(".discoveredDeviceTemplate");
365
+ /** @type {Object.<string, HTMLElement>} */
366
+ let discoveredDeviceEntities = {};
367
+
368
+ const toggleShowDiscoveredDevicesEntity = scene.querySelector(".toggleShowDiscoveredDevices");
369
+ client.addEventListener("isConnected", () => {
370
+ toggleShowDiscoveredDevicesEntity.setAttribute("fingertip-button", {
371
+ disabled: !client.isConnected,
372
+ });
373
+ });
374
+
375
+ toggleShowDiscoveredDevicesEntity.addEventListener("click", () => {
376
+ if (screenMode == "discoveredDevices") {
377
+ setScreenMode("none");
378
+ } else {
379
+ setScreenMode("discoveredDevices");
380
+ }
381
+ });
382
+
383
+ client.addEventListener("discoveredDevice", (event) => {
384
+ /** @type {BS.DiscoveredDevice} */
385
+ const discoveredDevice = event.message.discoveredDevice;
386
+ let discoveredDeviceEntity = discoveredDeviceEntities[discoveredDevice.bluetoothId];
387
+ if (!discoveredDeviceEntity) {
388
+ discoveredDeviceEntity = discoveredDeviceEntityTemplate.content.cloneNode(true).querySelector(".discoveredDevice");
389
+
390
+ discoveredDeviceEntity.addEventListener("click", () => {
391
+ let device = client.devices[discoveredDevice.bluetoothId];
392
+ console.log("discoveredDeviceEntity touch");
393
+ if (device) {
394
+ console.log("toggle connection", device);
395
+ device.toggleConnection();
396
+ } else {
397
+ device = client.connectToDevice(discoveredDevice.bluetoothId);
398
+ console.log("created", device);
399
+ }
400
+ onDevice(device);
401
+ });
402
+
403
+ const deviceIsConnectedListener = (event) => {
404
+ /** @type {BS.Device} */
405
+ const device = event.message.device;
406
+ console.log("deviceIsConnected", device);
407
+ if (device.bluetoothId != discoveredDevice.bluetoothId) {
408
+ return;
409
+ }
410
+ onDevice(device);
411
+ };
412
+ BS.DeviceManager.AddEventListener("deviceIsConnected", deviceIsConnectedListener);
413
+
414
+ let addedEventListeners = false;
415
+ /** @param {BS.Device} device */
416
+ const onDevice = (device) => {
417
+ if (addedEventListeners) {
418
+ return;
419
+ }
420
+ addedEventListeners = true;
421
+
422
+ console.log("onDevice", device);
423
+ device.addEventListener("connectionStatus", () => {
424
+ updateDiscoveredDeviceEntity(discoveredDevice);
425
+ });
426
+ updateDiscoveredDeviceEntity(discoveredDevice);
427
+ BS.DeviceManager.RemoveEventListener("deviceIsConnected", deviceIsConnectedListener);
428
+ };
429
+
430
+ let device = client.devices[discoveredDevice.bluetoothId];
431
+ if (device) {
432
+ onDevice(device);
433
+ }
434
+
435
+ discoveredDeviceEntities[discoveredDevice.bluetoothId] = discoveredDeviceEntity;
436
+ discoveredDevicesEntity.appendChild(discoveredDeviceEntity);
437
+ }
438
+
439
+ updateDiscoveredDeviceEntity(discoveredDevice);
440
+ });
441
+
442
+ /** @param {BS.DiscoveredDevice} discoveredDevice */
443
+ function updateDiscoveredDeviceEntity(discoveredDevice) {
444
+ const discoveredDeviceEntity = discoveredDeviceEntities[discoveredDevice.bluetoothId];
445
+ if (!discoveredDeviceEntity) {
446
+ console.warn(`no discoveredDeviceEntity for device id ${discoveredDevice.bluetoothId}`);
447
+ return;
448
+ }
449
+
450
+ const device = client.devices[discoveredDevice.bluetoothId];
451
+ const connectionStatus = device?.connectionStatus || "notConnected";
452
+ let connectMessage;
453
+ let disabled;
454
+ switch (connectionStatus) {
455
+ case "connected":
456
+ case "notConnected":
457
+ connectMessage = device?.isConnected ? "disconnect" : "connect";
458
+ disabled = false;
459
+ break;
460
+ case "connecting":
461
+ case "disconnecting":
462
+ connectMessage = connectionStatus;
463
+ disabled = true;
464
+ break;
465
+ }
466
+
467
+ if (screenMode != "discoveredDevices") {
468
+ disabled = true;
469
+ }
470
+
471
+ const text = [
472
+ device?.name || discoveredDevice.name,
473
+ device?.type || discoveredDevice.deviceType,
474
+ `rssi: ${discoveredDevice.rssi}`,
475
+ connectMessage,
476
+ ].join("\n");
477
+
478
+ if (discoveredDeviceEntity.hasLoaded) {
479
+ discoveredDeviceEntity.setAttribute("fingertip-button", { text, disabled });
480
+ } else {
481
+ discoveredDeviceEntity.addEventListener("loaded", () => updateDiscoveredDeviceEntity(discoveredDevice), {
482
+ once: true,
483
+ });
484
+ }
485
+ }
486
+
487
+ window.addEventListener("screenMode", () => {
488
+ const isDiscoveredDevicesMode = screenMode == "discoveredDevices";
489
+ const text = [isDiscoveredDevicesMode ? "hide" : "show", "discovered", "devices"].join("\n");
490
+ toggleShowDiscoveredDevicesEntity.setAttribute("fingertip-button", {
491
+ text,
492
+ });
493
+ discoveredDevicesEntity.object3D.visible = isDiscoveredDevicesMode;
494
+ Object.entries(client.discoveredDevices).forEach(([deviceId, discoveredDevice]) => {
495
+ updateDiscoveredDeviceEntity(discoveredDevice);
496
+ });
497
+ });
498
+
499
+ /** @param {BS.DiscoveredDevice} discoveredDevice */
500
+ function removeDiscoveredDeviceEntity(discoveredDevice) {
501
+ const discoveredDeviceEntity = discoveredDeviceEntities[discoveredDevice.bluetoothId];
502
+ if (!discoveredDeviceEntity) {
503
+ console.warn(`no discoveredDeviceEntity for device id ${discoveredDevice.bluetoothId}`);
504
+ return;
505
+ }
506
+
507
+ discoveredDeviceEntity.remove();
508
+ delete discoveredDeviceEntities[discoveredDevice.bluetoothId];
509
+ }
510
+
511
+ client.addEventListener("expiredDiscoveredDevice", (event) => {
512
+ /** @type {BS.DiscoveredDevice} */
513
+ const discoveredDevice = event.message.discoveredDevice;
514
+ removeDiscoveredDeviceEntity(discoveredDevice);
515
+ });
516
+
517
+ function clearDiscoveredDevices() {
518
+ discoveredDevicesEntity.querySelectorAll(".discoveredDevice").forEach((entity) => entity.remove());
519
+ discoveredDeviceEntities = {};
520
+ }
521
+
522
+ client.addEventListener("notConnected", () => {
523
+ clearDiscoveredDevices();
524
+ });
525
+
526
+ client.addEventListener("isScanning", () => {
527
+ if (client.isScanning) {
528
+ clearDiscoveredDevices();
529
+ }
530
+ });
531
+
532
+ // AVAILABLE DEVICES
533
+
534
+ const availableDevicesEntity = scene.querySelector(".availableDevices");
535
+ /** @type {HTMLTemplateElement} */
536
+ const availableDeviceEntityTemplate = availableDevicesEntity.querySelector(".availableDeviceTemplate");
537
+ /** @type {Object.<string, HTMLElement>} */
538
+ let availableDeviceEntities = {};
539
+
540
+ const toggleShowAvailableDevicesEntity = scene.querySelector(".toggleShowAvailableDevices");
541
+ client.addEventListener("isConnected", () => {
542
+ toggleShowAvailableDevicesEntity.setAttribute("fingertip-button", {
543
+ disabled: !client.isConnected,
544
+ });
545
+ });
546
+
547
+ toggleShowAvailableDevicesEntity.addEventListener("click", () => {
548
+ if (screenMode == "availableDevices") {
549
+ setScreenMode("none");
550
+ } else {
551
+ setScreenMode("availableDevices");
552
+ }
553
+ });
554
+
555
+ BS.DeviceManager.AddEventListener("availableDevices", (event) => {
556
+ /** @type {BS.Device[]} */
557
+ const availableDevices = event.message.availableDevices;
558
+ console.log({ availableDevices });
559
+
560
+ availableDevices.forEach((device) => {
561
+ if (device.connectionType != "webSocketClient" || !device.bluetoothId) {
562
+ return;
563
+ }
564
+
565
+ let availableDeviceEntity = availableDeviceEntities[device.bluetoothId];
566
+ if (!availableDeviceEntity) {
567
+ availableDeviceEntity = availableDeviceEntityTemplate.content.cloneNode(true).querySelector(".availableDevice");
568
+
569
+ availableDeviceEntity.addEventListener("click", () => {
570
+ console.log("availableDeviceEntity", "click", device);
571
+ device.toggleConnection();
572
+ });
573
+
574
+ device.addEventListener("connectionStatus", () => {
575
+ updateAvailableDeviceEntity(device);
576
+ });
577
+
578
+ availableDeviceEntities[device.bluetoothId] = availableDeviceEntity;
579
+ availableDevicesEntity.appendChild(availableDeviceEntity);
580
+ }
581
+
582
+ updateAvailableDeviceEntity(device);
583
+ });
584
+ });
585
+
586
+ /** @param {BS.Device} device */
587
+ function updateAvailableDeviceEntity(device) {
588
+ const availableDeviceEntity = availableDeviceEntities[device.bluetoothId];
589
+ if (!availableDeviceEntity) {
590
+ console.warn(`no availableDeviceEntity for device id ${device.bluetoothId}`);
591
+ return;
592
+ }
593
+
594
+ console.log("updateAvailableDeviceEntity", device);
595
+
596
+ let connectMessage;
597
+ let disabled;
598
+ switch (device.connectionStatus) {
599
+ case "connected":
600
+ case "notConnected":
601
+ connectMessage = device.isConnected ? "disconnect" : "connect";
602
+ disabled = false;
603
+ break;
604
+ case "connecting":
605
+ case "disconnecting":
606
+ connectMessage = device.connectionStatus;
607
+ disabled = true;
608
+ break;
609
+ }
610
+
611
+ if (screenMode != "availableDevices") {
612
+ disabled = true;
613
+ }
614
+
615
+ const text = [device.name, device.type, connectMessage].filter(Boolean).join("\n");
616
+
617
+ if (availableDeviceEntity.hasLoaded) {
618
+ availableDeviceEntity.setAttribute("fingertip-button", { text, disabled });
619
+ } else {
620
+ availableDeviceEntity.addEventListener("loaded", () => updateAvailableDeviceEntity(device), { once: true });
621
+ }
622
+ }
623
+
624
+ window.addEventListener("screenMode", () => {
625
+ const isAvailableDevicesMode = screenMode == "availableDevices";
626
+ const text = [isAvailableDevicesMode ? "hide" : "show", "available", "devices"].join("\n");
627
+ toggleShowAvailableDevicesEntity.setAttribute("fingertip-button", {
628
+ text,
629
+ });
630
+ availableDevicesEntity.object3D.visible = isAvailableDevicesMode;
631
+ BS.DeviceManager.AvailableDevices.forEach((availableDevice) => {
632
+ updateAvailableDeviceEntity(availableDevice);
633
+ });
634
+ });
635
+
636
+ function clearAvailableDevices() {
637
+ availableDevicesEntity.querySelectorAll(".availableDevice").forEach((entity) => entity.remove());
638
+ availableDeviceEntities = {};
639
+ }
640
+
641
+ client.addEventListener("notConnected", () => {
642
+ clearAvailableDevices();
643
+ });
644
+
645
+ // SENSOR DATA
646
+
647
+ let sensorDataRate = 20;
648
+
649
+ // POSITION MODE
650
+
651
+ /** @typedef {"none" | "linearAcceleration" | "gravity" | "acceleration"} PositionMode */
652
+ /** @type {PositionMode[]} */
653
+ const positionModes = ["none", "acceleration", "gravity", "linearAcceleration"];
654
+ /** @type {PositionMode} */
655
+ let positionMode = "none";
656
+
657
+ /** @param {PositionMode} newPositionMode */
658
+ function setPositionMode(newPositionMode) {
659
+ if (positionMode == newPositionMode) {
660
+ console.log("redundant positionMode assignment", newPositionMode);
661
+ return;
662
+ }
663
+ if (!positionModes.includes(newPositionMode)) {
664
+ console.error(`invalid positionMode ${newPositionMode}`);
665
+ return;
666
+ }
667
+ positionMode = newPositionMode;
668
+
669
+ /** @type {BS.SensorConfiguration} */
670
+ const sensorConfiguration = {
671
+ linearAcceleration: 0,
672
+ acceleration: 0,
673
+ gravity: 0,
674
+ };
675
+ if (positionMode != "none") {
676
+ sensorConfiguration[positionMode] = sensorDataRate;
677
+ }
678
+
679
+ console.log(sensorConfiguration);
680
+ devicePair.setSensorConfiguration(sensorConfiguration);
681
+
682
+ window.dispatchEvent(new CustomEvent("positionMode", { detail: { positionMode } }));
683
+ }
684
+
685
+ // ROTATION MODE
686
+
687
+ /** @typedef {"none" | "rotation" | "gameRotation" | "gyroscope"} OrientationMode */
688
+ /** @type {OrientationMode[]} */
689
+ const orientationModes = ["none", "rotation", "gameRotation", "gyroscope"];
690
+ /** @type {OrientationMode} */
691
+ let orientationMode = "none";
692
+
693
+ /** @param {OrientationMode} newOrientationMode */
694
+ function setOrientationMode(newOrientationMode) {
695
+ if (orientationMode == newOrientationMode) {
696
+ console.log("redundant orientationMode assignment", newOrientationMode);
697
+ return;
698
+ }
699
+ if (!orientationModes.includes(newOrientationMode)) {
700
+ console.error(`invalid orientationMode ${newOrientationMode}`);
701
+ return;
702
+ }
703
+ orientationMode = newOrientationMode;
704
+
705
+ /** @type {BS.SensorConfiguration} */
706
+ const sensorConfiguration = {
707
+ gameRotation: 0,
708
+ rotation: 0,
709
+ gyroscope: 0,
710
+ };
711
+ if (orientationMode != "none") {
712
+ sensorConfiguration[orientationMode] = sensorDataRate;
713
+ }
714
+
715
+ console.log(sensorConfiguration);
716
+ devicePair.setSensorConfiguration(sensorConfiguration);
717
+
718
+ window.dispatchEvent(new CustomEvent("orientationMode", { detail: { orientationMode } }));
719
+ }
720
+
721
+ // PRESSURE MODE
722
+
723
+ /** @typedef {"none" | "pressure"} PressureMode */
724
+ /** @type {PressureMode[]} */
725
+ const pressureModes = ["none", "pressure"];
726
+ /** @type {PressureMode} */
727
+ let pressureMode = "none";
728
+
729
+ /** @param {PressureMode} newPressureMode */
730
+ function setPressureMode(newPressureMode) {
731
+ if (pressureMode == newPressureMode) {
732
+ console.log("redundant pressureMode assignment", newPressureMode);
733
+ return;
734
+ }
735
+ if (!pressureModes.includes(newPressureMode)) {
736
+ console.error(`invalid pressureMode ${newPressureMode}`);
737
+ return;
738
+ }
739
+ pressureMode = newPressureMode;
740
+
741
+ /** @type {BS.SensorConfiguration} */
742
+ const sensorConfiguration = {
743
+ pressure: 0,
744
+ };
745
+ if (pressureMode != "none") {
746
+ sensorConfiguration[pressureMode] = sensorDataRate;
747
+ }
748
+
749
+ console.log(sensorConfiguration);
750
+ devicePair.setSensorConfiguration(sensorConfiguration);
751
+
752
+ window.dispatchEvent(new CustomEvent("pressureMode", { detail: { pressureMode } }));
753
+ }
754
+
755
+ // DEVICE PAIR
756
+
757
+ const devicePairEntity = scene.querySelector(".devicePair");
758
+
759
+ /** @type {BS.SensorType[]} */
760
+ const sensorTypes = ["pressure", "linearAcceleration", "gameRotation", "gyroscope"];
761
+ /** @type {HTMLTemplateElement} */
762
+ const toggleSensorTypeEntityTemplate = devicePairEntity.querySelector(".toggleSensorTypeTemplate");
763
+
764
+ /** @type {Map.<SensorType, HTMLElement>} */
765
+ const toggleSensorTypeEntities = new Map();
766
+
767
+ sensorTypes.forEach((sensorType, index) => {
768
+ /** @type {HTMLElement} */
769
+ const toggleSensorTypeEntity = toggleSensorTypeEntityTemplate.content
770
+ .cloneNode(true)
771
+ .querySelector(".toggleSensorType");
772
+ toggleSensorTypeEntities.set(sensorType, toggleSensorTypeEntity);
773
+
774
+ toggleSensorTypeEntity.addEventListener("click", () => {
775
+ if (positionModes.includes(sensorType)) {
776
+ setPositionMode(positionMode == sensorType ? "none" : sensorType);
777
+ }
778
+ if (orientationModes.includes(sensorType)) {
779
+ setOrientationMode(orientationMode == sensorType ? "none" : sensorType);
780
+ }
781
+ if (pressureModes.includes(sensorType)) {
782
+ setPressureMode(pressureMode == sensorType ? "none" : sensorType);
783
+ }
784
+ });
785
+
786
+ updateToggleSensorTypeEntity(sensorType);
787
+ devicePairEntity.appendChild(toggleSensorTypeEntity);
788
+ });
789
+
790
+ function updateToggleSensorTypeEntity(sensorType) {
791
+ const toggleSensorTypeEntity = toggleSensorTypeEntities.get(sensorType);
792
+ if (!toggleSensorTypeEntity) {
793
+ console.log(`no toggleSensorTypeEntity found for sensorType "${sensorType}"`);
794
+ return;
795
+ }
796
+
797
+ let isSensorTypeEnabled = false;
798
+ if (positionModes.includes(sensorType)) {
799
+ isSensorTypeEnabled = positionMode == sensorType;
800
+ }
801
+ if (orientationModes.includes(sensorType)) {
802
+ isSensorTypeEnabled = orientationMode == sensorType;
803
+ }
804
+ if (pressureModes.includes(sensorType)) {
805
+ isSensorTypeEnabled = pressureMode == sensorType;
806
+ }
807
+
808
+ const sensorTypeStrings = sensorType.split(/(?=[A-Z])/g).map((string) => string.toLowerCase());
809
+ const text = [isSensorTypeEnabled ? "disable" : "enable", ...sensorTypeStrings].join("\n");
810
+
811
+ const disabled = screenMode != "devicePair" || !devicePair.isPartiallyConnected;
812
+
813
+ if (toggleSensorTypeEntity.hasLoaded) {
814
+ toggleSensorTypeEntity.setAttribute("fingertip-button", {
815
+ text,
816
+ disabled,
817
+ });
818
+ } else {
819
+ toggleSensorTypeEntity.addEventListener("loaded", () => updateToggleSensorTypeEntity(sensorType), {
820
+ once: true,
821
+ });
822
+ }
823
+ }
824
+
825
+ function updateToggleSensorTypeEntities() {
826
+ toggleSensorTypeEntities.forEach((toggleSensorTypeEntity, sensorType) => {
827
+ updateToggleSensorTypeEntity(sensorType);
828
+ });
829
+ }
830
+
831
+ devicePair.addEventListener("deviceIsConnected", () => {
832
+ updateToggleSensorTypeEntities();
833
+ });
834
+
835
+ const toggleShowDevicePairEntity = scene.querySelector(".toggleShowDevicePair");
836
+ client.addEventListener("isConnected", () => {
837
+ toggleShowDevicePairEntity.setAttribute("fingertip-button", {
838
+ disabled: !client.isConnected,
839
+ });
840
+ });
841
+
842
+ toggleShowDevicePairEntity.addEventListener("click", () => {
843
+ if (screenMode == "devicePair") {
844
+ setScreenMode("none");
845
+ } else {
846
+ setScreenMode("devicePair");
847
+ }
848
+ });
849
+
850
+ window.addEventListener("screenMode", () => {
851
+ const isDevicePairMode = screenMode == "devicePair";
852
+ const text = [isDevicePairMode ? "hide" : "show", "device", "pair"].join("\n");
853
+ toggleShowDevicePairEntity.setAttribute("fingertip-button", {
854
+ text,
855
+ });
856
+ devicePairEntity.object3D.visible = isDevicePairMode;
857
+ updateToggleSensorTypeEntities();
858
+ });
859
+
860
+ window.addEventListener("orientationMode", () => {
861
+ updateToggleSensorTypeEntities();
862
+ });
863
+ window.addEventListener("positionMode", () => {
864
+ updateToggleSensorTypeEntities();
865
+ });
866
+ window.addEventListener("pressureMode", () => {
867
+ updateToggleSensorTypeEntities();
868
+ });
869
+
870
+ // MOTION
871
+
872
+ /** @type {HTMLTemplateElement} */
873
+ const insoleMotionTemplate = document.getElementById("insoleMotionTemplate");
874
+
875
+ let positionInterpolationSmoothing = 0.4;
876
+ let orientationInterpolationSmoothing = 0.4;
877
+ let gyroscopeScalar = 0.5;
878
+
879
+ let positionScalar = 0.1;
880
+
881
+ devicePair.sides.forEach((side) => {
882
+ /** @type {HTMLElement} */
883
+ const insoleMotionEntity = insoleMotionTemplate.content.cloneNode(true).querySelector(".insole.motion");
884
+ insoleMotionEntity.classList.add(side);
885
+
886
+ let scale = "1 1 1";
887
+ let position = "0 0 0";
888
+
889
+ switch (side) {
890
+ case "left":
891
+ position = "-0.1 0 0";
892
+ break;
893
+ case "right":
894
+ position = "0.1 0 0";
895
+ //scale = "-1 1 1";
896
+ break;
897
+ }
898
+ insoleMotionEntity.setAttribute("position", position);
899
+
900
+ const insoleModelEntity = insoleMotionEntity.querySelector(".model");
901
+ insoleModelEntity.setAttribute("gltf-model", `#${side}InsoleModel`);
902
+ insoleModelEntity.setAttribute("scale", scale);
903
+
904
+ const insolePositionEntity = insoleMotionEntity.querySelector(".position");
905
+
906
+ const insoleOrientationEntity = insoleMotionEntity.querySelector(".orientation");
907
+
908
+ // POSITION
909
+
910
+ const interpolatedPosition = new THREE.Vector3();
911
+
912
+ /** @param {BS.Vector3} position */
913
+ const updatePosition = (position) => {
914
+ interpolatedPosition.copy(position).multiplyScalar(positionScalar);
915
+ insolePositionEntity.object3D.position.lerp(interpolatedPosition, positionInterpolationSmoothing);
916
+ };
917
+
918
+ // ORIENATION
919
+
920
+ const latestQuaternion = new THREE.Quaternion();
921
+ const offsetQuaternion = new THREE.Quaternion();
922
+ const resetOrientation = () => {
923
+ offsetQuaternion.copy(latestQuaternion).invert();
924
+ };
925
+ window.addEventListener("resetOrientation", () => resetOrientation());
926
+
927
+ const targetQuaternion = new THREE.Quaternion();
928
+ /**
929
+ * @param {BS.Quaternion} quaternion
930
+ * @param {boolean} applyOffset
931
+ */
932
+ const updateOrientation = (quaternion, applyOffset = false) => {
933
+ latestQuaternion.copy(quaternion);
934
+ targetQuaternion.copy(quaternion);
935
+ if (applyOffset) {
936
+ targetQuaternion.premultiply(offsetQuaternion);
937
+ }
938
+ insoleOrientationEntity.object3D.quaternion.slerp(targetQuaternion, orientationInterpolationSmoothing);
939
+ };
940
+
941
+ // DEVICE SENSOR DATA
942
+
943
+ const gyroscopeVector3 = new THREE.Vector3();
944
+ const gyroscopeEuler = new THREE.Euler();
945
+ const gyroscopeQuaternion = new THREE.Quaternion();
946
+
947
+ devicePair.addEventListener("deviceSensorData", (event) => {
948
+ /** @type {BS.Device} */
949
+ const device = event.message.device;
950
+
951
+ if (device.insoleSide != side) {
952
+ return;
953
+ }
954
+
955
+ /** @type {BS.SensorType} */
956
+ const sensorType = event.message.sensorType;
957
+
958
+ if (sensorType == positionMode) {
959
+ switch (sensorType) {
960
+ case "acceleration":
961
+ case "gravity":
962
+ case "linearAcceleration":
963
+ {
964
+ /** @type {BS.Vector3} */
965
+ const position = event.message[sensorType];
966
+ updatePosition(position);
967
+ }
968
+ break;
969
+ }
970
+ }
971
+
972
+ if (sensorType == orientationMode) {
973
+ switch (sensorType) {
974
+ case "gyroscope":
975
+ {
976
+ const vector = event.message.gyroscope;
977
+ gyroscopeVector3
978
+ .copy(vector)
979
+ .multiplyScalar(Math.PI / 180)
980
+ .multiplyScalar(gyroscopeScalar);
981
+ gyroscopeEuler.setFromVector3(gyroscopeVector3);
982
+ gyroscopeQuaternion.setFromEuler(gyroscopeEuler);
983
+ updateOrientation(gyroscopeQuaternion, false);
984
+ }
985
+ break;
986
+ case "gameRotation":
987
+ case "rotation":
988
+ {
989
+ const quaternion = event.message[sensorType];
990
+ updateOrientation(quaternion, true);
991
+ }
992
+ break;
993
+ }
994
+ }
995
+ });
996
+
997
+ desktopEntity.appendChild(insoleMotionEntity);
998
+ });
999
+
1000
+ // RESET ORIENTATION
1001
+
1002
+ const resetOrientationEntity = devicePairEntity.querySelector(".resetOrientation");
1003
+ resetOrientationEntity.addEventListener("click", () => {
1004
+ window.dispatchEvent(new CustomEvent("resetOrientation"));
1005
+ });
1006
+
1007
+ function updateResetOrientationEntity() {
1008
+ resetOrientationEntity.setAttribute("fingertip-button", {
1009
+ disabled: screenMode != "devicePair" || !devicePair.isPartiallyConnected,
1010
+ });
1011
+ }
1012
+
1013
+ window.addEventListener("screenMode", () => {
1014
+ updateResetOrientationEntity();
1015
+ });
1016
+
1017
+ devicePair.addEventListener("deviceIsConnected", () => {
1018
+ updateResetOrientationEntity();
1019
+ });
1020
+
1021
+ // PRESSURE
1022
+
1023
+ /** @type {HTMLTemplateElement} */
1024
+ const insolePressureEntityTemplate = document.getElementById("insolePressureTemplate");
1025
+ /** @type {HTMLTemplateElement} */
1026
+ const insolePressureSensorEntityTemplate = document.getElementById("insolePressureSensorTemplate");
1027
+
1028
+ const numberOfPressureSensors = 8;
1029
+
1030
+ devicePair.sides.forEach((side) => {
1031
+ /** @type {HTMLElement} */
1032
+ const insolePressureEntity = insolePressureEntityTemplate.content.cloneNode(true).querySelector(".insole.pressure");
1033
+ insolePressureEntity.classList.add(side);
1034
+
1035
+ let scale = "1 1 1";
1036
+ let position = "0 0 0";
1037
+
1038
+ switch (side) {
1039
+ case "left":
1040
+ position = "-0.2 0 0";
1041
+ scale = "-1 1 1";
1042
+ break;
1043
+ case "right":
1044
+ position = "0.2 0 0";
1045
+ break;
1046
+ }
1047
+ insolePressureEntity.setAttribute("position", position);
1048
+
1049
+ const insoleImagesEntity = insolePressureEntity.querySelector(".images");
1050
+ insoleImagesEntity.setAttribute("scale", scale);
1051
+
1052
+ const pressureSensorEntities = [];
1053
+ for (let index = 0; index < numberOfPressureSensors; index++) {
1054
+ /** @type {HTMLElement} */
1055
+ const insolePressureSensorEntity = insolePressureSensorEntityTemplate.content
1056
+ .cloneNode(true)
1057
+ .querySelector(".insolePressureSensor");
1058
+ insolePressureSensorEntity.setAttribute("src", `#pressureSensorImage${index}`);
1059
+ insoleImagesEntity.appendChild(insolePressureSensorEntity);
1060
+ pressureSensorEntities[index] = insolePressureSensorEntity;
1061
+ }
1062
+
1063
+ insoleImagesEntity.querySelectorAll("a-image").forEach((imageEntity) => {
1064
+ const updateSize = () => {
1065
+ let intervalId = setInterval(() => {
1066
+ const image = imageEntity.components?.["material"]?.material?.map?.source?.data;
1067
+ if (!image) {
1068
+ return;
1069
+ }
1070
+ clearInterval(intervalId);
1071
+
1072
+ const { width, height } = image;
1073
+ const imageRatio = width / height;
1074
+ imageEntity.setAttribute("scale", `${imageRatio} 1 1`);
1075
+ }, 1);
1076
+ };
1077
+ if (imageEntity.hasLoaded) {
1078
+ updateSize();
1079
+ } else {
1080
+ imageEntity.addEventListener("loaded", () => updateSize());
1081
+ }
1082
+ });
1083
+
1084
+ devicePair.addEventListener("devicePressure", (event) => {
1085
+ /** @type {BS.Device} */
1086
+ const device = event.message.device;
1087
+
1088
+ if (device.insoleSide != side) {
1089
+ return;
1090
+ }
1091
+
1092
+ /** @type {BS.PressureData} */
1093
+ const pressure = event.message.pressure;
1094
+
1095
+ pressure.sensors.forEach((sensor, index) => {
1096
+ pressureSensorEntities[index].components["material"].material.opacity = sensor.normalizedValue;
1097
+ });
1098
+ });
1099
+
1100
+ desktopEntity.appendChild(insolePressureEntity);
1101
+ });
1102
+
1103
+ // VIBRATION
1104
+
1105
+ const vibrateDevicePairEntity = devicePairEntity.querySelector(".vibrate");
1106
+ vibrateDevicePairEntity.addEventListener("click", () => {
1107
+ devicePair.triggerVibration([
1108
+ {
1109
+ type: "waveformEffect",
1110
+ waveformEffect: { segments: [{ effect: "strongBuzz100" }] },
1111
+ },
1112
+ ]);
1113
+ });
1114
+
1115
+ function updateVibrateEntity() {
1116
+ vibrateDevicePairEntity.setAttribute("fingertip-button", {
1117
+ disabled: screenMode != "devicePair" || !devicePair.isPartiallyConnected,
1118
+ });
1119
+ }
1120
+
1121
+ window.addEventListener("screenMode", () => {
1122
+ updateVibrateEntity();
1123
+ });
1124
+
1125
+ devicePair.addEventListener("deviceIsConnected", () => {
1126
+ updateVibrateEntity();
1127
+ });